perf: achieve 100/100 react-doctor score#155
Conversation
|
CodeAnt AI is reviewing your PR. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR updates security and markup handling, consolidates auth and Supabase client usage, refactors pipeline and data-fetching flows, migrates UI primitives and animation wrappers, modernizes the form subsystem, and revises templates, docs, and content. ChangesFrontend Junction Modernization
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| @@ -1,5 +1,5 @@ | |||
| import LandingPage from '@/components/home'; | |||
| import useGetCompanies from '@/hooks/useGetCompanies'; | |||
| import getCompanies from '@/hooks/useGetCompanies'; | |||
There was a problem hiding this comment.
Suggestion: The homepage switched to @/hooks/useGetCompanies, which uses the browser Supabase client (NEXT_PUBLIC_SUPABASE_ANON_KEY) and queries the company table, while the existing server-side company fetcher uses service-role access and aggregates from scraped_experiences/new_interview. In production with RLS or without a populated company table, this will silently return empty data for SSR. Use the server data-access function for this server component instead of the hook module. [api mismatch]
Severity Level: Major ⚠️
- ⚠️ Homepage companies fetch may silently return empty array.
- ⚠️ Companies data source inconsistent across homepage and company pages.
- ⚠️ Supabase RLS errors hidden by catch-and-log behavior.Steps of Reproduction ✅
1. Start the Next.js app and request the homepage `/`, which is implemented by the server
component `Home` in `app/page.tsx:88-107` (default export async function).
2. During SSR, `Home` calls `getCachedCompanies()` from `app/page.tsx:63-70`, which
invokes `getCompanies` imported from `@/hooks/useGetCompanies` (import at
`app/page.tsx:2`) inside the cached async function.
3. The `getCompanies` implementation in `hooks/useGetCompanies.ts:6-18` uses `supabase`
from `@/lib/supabase` (see `lib/supabase.ts:1-6`), which in turn creates a browser
Supabase client with `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` via
`createBrowserClient` in `utils/supabase/client.ts:1-7`, then queries the `company` table
(`hooks/useGetCompanies.ts:8-10`) and returns transformed data or, on error/RLS denial,
falls into the catch block and returns `undefined`.
4. When the anon key lacks RLS permission to read the `company` table or that table is
empty, `getCompanies` returns `undefined` or an empty array; `getCachedCompanies` then
executes `return companies || [];` (`app/page.tsx:65-67`), so the SSR response always
receives an empty companies list without surfacing the underlying access issue.
5. In contrast, other server code paths like `app/companies/page.tsx:1-4,24` and
`app/companies/[company]/page.tsx:2,16,49` import `getCompanies` from `@/lib/getCompanies`
(`lib/getCompanies.ts:3-57`), which uses `SUPABASE_SERVICE_ROLE_KEY` and aggregates from
`scraped_experiences` and `new_interview` tables, meaning the homepage now uses a weaker,
potentially-empty data source than the rest of the site under realistic production RLS
settings.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** app/page.tsx
**Line:** 2:2
**Comment:**
*Api Mismatch: The homepage switched to `@/hooks/useGetCompanies`, which uses the browser Supabase client (`NEXT_PUBLIC_SUPABASE_ANON_KEY`) and queries the `company` table, while the existing server-side company fetcher uses service-role access and aggregates from `scraped_experiences`/`new_interview`. In production with RLS or without a populated `company` table, this will silently return empty data for SSR. Use the server data-access function for this server component instead of the hook module.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| blog_link: isExclusive ? null : formData.link, | ||
| is_exclusive: isExclusive, | ||
| blog_link: isExclusive.current ? null : formData.link, | ||
| is_exclusive: isExclusive.current, |
There was a problem hiding this comment.
Suggestion: This writes the exclusivity flag as is_exclusive, but downstream list/admin consumers use isExclusive and the data fetch/mapping path does not include this new field, so exclusive posts will be treated as non-exclusive (wrong badges/stats/behavior). Propagate this field through the fetch/select and transform layers. [api mismatch]
Severity Level: Major ⚠️
- ⚠️ Admin dashboard exclusive count omits new exclusive submissions.
- ⚠️ Experience cards never show Exclusive badge for new posts.
- ⚠️ Any exclusivity-based logic cannot see new experiences.Steps of Reproduction ✅
1. Navigate to the add experience page at `app/add-experience/page.tsx:20-43`, which
renders `InterviewExperienceForm` from `components/add-new-experience.tsx:85` as the main
content.
2. In `InterviewExperienceForm` (`components/add-new-experience.tsx:85-151`), switch the
tabs to the exclusive mode so `onValueChange` sets `isExclusive.current = val ===
'exclusive'` (`components/add-new-experience.tsx:255-260`), then enter interview content
into the exclusive text editor.
3. Submit the form; `handleSubmit` (`components/add-new-experience.tsx:151-203`) builds
`experienceData` with `description: isExclusive.current ? exclusiveContent : ''`,
`blog_link: isExclusive.current ? null : formData.link`, and `is_exclusive:
isExclusive.current` (`components/add-new-experience.tsx:163-175`), then inserts this row
into the Supabase `new_interview` table via `supabase.from('new_interview').insert(...)`
(`components/add-new-experience.tsx:180-182`).
4. An admin visits `/admin`, which renders `AdminDashboard` (`app/admin/page.tsx:1-9`).
`AdminDashboard` uses `useExperiences` (`components/admin-dashboard.tsx:35-37`), which
calls `getExperiences` (`hooks/getExperiences.ts:3-16`). `getExperiences` queries
`new_interview` without selecting `is_exclusive` (`hooks/getExperiences.ts:17-21`), and
`transformNewData` maps each record to an experience object without any `isExclusive`
property (`hooks/getExperiences.ts:99-116`). As a result, `Experience.isExclusive` in
`AdminDashboard` (`components/admin-dashboard.tsx:18-24`) and the `isExclusive` prop in
`CardComponent` (`components/common/card.tsx:23,257,440-443`) are always `undefined`, so
exclusive posts are treated as non-exclusive in stats
(`components/admin-dashboard.tsx:54-63`) and card badges
(`components/common/card.tsx:440-443`).(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** components/add-new-experience.tsx
**Line:** 174:174
**Comment:**
*Api Mismatch: This writes the exclusivity flag as `is_exclusive`, but downstream list/admin consumers use `isExclusive` and the data fetch/mapping path does not include this new field, so exclusive posts will be treated as non-exclusive (wrong badges/stats/behavior). Propagate this field through the fetch/select and transform layers.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| } | ||
|
|
||
| export async function GET(request: Request) { | ||
| export async function POST(request: Request) { |
There was a problem hiding this comment.
Suggestion: Changing this handler to POST breaks existing GET-based integrations (common for cron/scheduler triggers), causing those callers to receive method errors and stop processing. Keep backward compatibility by retaining a GET handler (or adding one that delegates to POST) if existing jobs still call this endpoint via GET. [api mismatch]
Severity Level: Major ⚠️
- ❌ GET requests to /api/pipeline/process no longer work.
- ⚠️ Existing GET-based cron jobs stop triggering pipeline.Steps of Reproduction ✅
1. Run the Next.js app with the current PR code; verify in
`app/api/pipeline/process/route.ts` that only a POST handler exists (lines 39–225) and
there is no exported GET function (confirmed via BulkRead of this file).
2. Send an HTTP GET request to the `/api/pipeline/process` endpoint with a valid key query
parameter (App Router maps `app/api/pipeline/process/route.ts` to `/api/pipeline/process`
and the security check at lines 40–47 expects `key` in the URL).
3. Next.js attempts to dispatch the GET request but, because no `export async function
GET` exists in `app/api/pipeline/process/route.ts`, the framework responds with a
405-style method error and the processing logic at lines 58–221 is never executed.
4. Any existing external cron or scheduler configuration that previously triggered this
endpoint using GET (typical for HTTP cron jobs on function URLs) will now consistently
receive method errors instead of running the pipeline, effectively stopping scheduled
processing until those jobs are updated to use POST.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** app/api/pipeline/process/route.ts
**Line:** 39:39
**Comment:**
*Api Mismatch: Changing this handler to POST breaks existing GET-based integrations (common for cron/scheduler triggers), causing those callers to receive method errors and stop processing. Keep backward compatibility by retaining a GET handler (or adding one that delegates to POST) if existing jobs still call this endpoint via GET.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| import { TelegramSource } from '@/lib/content-pipeline/sources/telegram'; | ||
| import { HashnodeSource } from '@/lib/content-pipeline/sources/hashnode'; | ||
| import { ScrapedArticle } from '@/lib/content-pipeline/types'; | ||
| import { getAuthState } from '@/lib/auth'; |
There was a problem hiding this comment.
Suggestion: The new auth helper import points to @/lib/auth, but this module is not present in the current codebase, which will fail route compilation/loading at runtime. Update the import to an existing auth module (or add the missing file in the same PR) so this endpoint can be built and executed. [import error]
Severity Level: Critical 🚨
- ❌ /api/pipeline route fails due to missing auth module.
- ❌ Build or dev server may crash on module error.Steps of Reproduction ✅
1. Run `next dev` or `next build` with the current PR code; Next.js attempts to compile
the API route defined in `app/api/pipeline/route.ts` (BulkRead shows this file contains
the POST handler at lines 19–109).
2. During compilation, the bundler resolves the import at `app/api/pipeline/route.ts:9`
(`import { getAuthState } from '@/lib/auth';`).
3. A Glob search over `/workspace/frontend-junction` for `**/auth.*` and `lib/auth/**`
returns no files, and a Grep search for `getAuthState` shows only this import line,
confirming that the `@/lib/auth` module and `getAuthState` symbol do not exist in the
repository.
4. Because the import target is missing, the build tool emits a module resolution error
(for example, "Module not found: Can't resolve '@/lib/auth'"), causing the `/api/pipeline`
route to fail to load and potentially breaking the overall build or dev server startup.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** app/api/pipeline/route.ts
**Line:** 9:9
**Comment:**
*Import Error: The new auth helper import points to `@/lib/auth`, but this module is not present in the current codebase, which will fail route compilation/loading at runtime. Update the import to an existing auth module (or add the missing file in the same PR) so this endpoint can be built and executed.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| const upsertResults = await Promise.all( | ||
| seedData.map((exp) => | ||
| supabaseAdmin.from('scraped_experiences').upsert( | ||
| { | ||
| title: (exp as any).title, | ||
| original_url: (exp as any).original_url, | ||
| source: (exp as any).source, | ||
| author: (exp as any).author, | ||
| published_at: (exp as any).published_at || new Date().toISOString(), | ||
| tags: (exp as any).tags || [], | ||
| summary: (exp as any).summary || '', | ||
| formatted_content: (exp as any).formatted_content || '', | ||
| slug: (exp as any).slug || '', | ||
| metadata: {}, | ||
| status: 'approved', | ||
| ai_processed: true, | ||
| }, | ||
| { onConflict: 'original_url', ignoreDuplicates: false } | ||
| ) | ||
| ) | ||
| ); |
There was a problem hiding this comment.
Suggestion: Using Promise.all here means one rejected upsert aborts the whole batch and throws before per-item error aggregation runs, so partial failures turn into a full endpoint failure. Use Promise.allSettled (or per-item try/catch) so one failed record does not crash the entire seed operation. [logic error]
Severity Level: Major ⚠️
- ❌ Single upsert rejection can abort entire seed operation.
- ⚠️ Successful seeds not reported when one write fails.Steps of Reproduction ✅
1. Call `POST /api/seed-experiences` with the Authorization header `Bearer` plus the CRON
secret so the handler in `app/api/seed-experiences/route.ts` (lines 14–63) executes and
passes the auth check at lines 15–17.
2. The handler creates a Supabase admin client at lines 20–23 and then invokes
`Promise.all` at line 27 over `seedData.map(...)`, where each element awaits
`supabaseAdmin.from('scraped_experiences').upsert(...)` returning a promise that normally
resolves with `{ data, error }` but can reject on transport or client failures.
3. If any of these upsert operations rejects (for example, due to a transient network
error or Supabase client issue), `Promise.all` at line 27 rejects immediately and throws,
so the `upsertResults` array is never returned and the for-loop at lines 49–55 that
aggregates per-item errors into `results.errors` does not run.
4. With no surrounding try/catch around `Promise.all`, the rejection propagates as an
unhandled error from the route, causing the entire seed request to fail (likely with a
generic 500) instead of partially succeeding and reporting which individual records failed
via `results.errors`.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** app/api/seed-experiences/route.ts
**Line:** 27:47
**Comment:**
*Logic Error: Using `Promise.all` here means one rejected upsert aborts the whole batch and throws before per-item error aggregation runs, so partial failures turn into a full endpoint failure. Use `Promise.allSettled` (or per-item try/catch) so one failed record does not crash the entire seed operation.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
|
||
| // 1. Fetch the URL content | ||
| const response = await fetch(url); | ||
| const response = await fetch(url, { redirect: 'manual' }); |
There was a problem hiding this comment.
Suggestion: The new fetch call still uses user-supplied url directly with no host/protocol allowlist, so any authenticated user can force server-side requests to internal or sensitive endpoints (SSRF). Validate and restrict destinations (for example, only http/https and approved domains) before calling fetch. [ssrf]
Severity Level: Critical 🚨
- ❌ Summarize API allows server-side requests to arbitrary URLs.
- ❌ Internal services or metadata endpoints may be probed via SSRF.
- ⚠️ Admin summarization workflow vulnerable to malicious blogLink URLs.Steps of Reproduction ✅
1. Observe the summarize API handler at `app/api/summarize/route.ts:9-93`, where
`POST(req: Request)` reads `{ url }` from the JSON body (lines 35-39) and immediately uses
it in `fetch(url, { redirect: 'manual' });` (line 42) without any validation or
allowlisting.
2. Note that the handler enforces only that a Supabase auth session exists (lines 11-27),
so any authenticated user with a valid session cookie can call `/api/summarize` directly;
there is no additional role or origin restriction around the `url` input.
3. From a client such as curl or Postman, send a request `POST /api/summarize` with header
`Content-Type: application/json` and body `{"url":
"http://169.254.169.254/latest/meta-data/"}` while including a valid Supabase session
cookie so the request passes the session check.
4. When this request is processed, the code at `app/api/summarize/route.ts:41-43` causes
the server to issue an HTTP request to `http://169.254.169.254/latest/meta-data/` (or any
other attacker-chosen host) via `fetch`, demonstrating that server-side network access is
performed against a user-controlled, unvalidated URL (classic SSRF pattern) before the
response body is parsed and sent into the Gemini prompt.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** app/api/summarize/route.ts
**Line:** 42:42
**Comment:**
*Ssrf: The new fetch call still uses user-supplied `url` directly with no host/protocol allowlist, so any authenticated user can force server-side requests to internal or sensitive endpoints (SSRF). Validate and restrict destinations (for example, only `http/https` and approved domains) before calling `fetch`.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
CodeAnt AI finished reviewing your PR. |
…to feat/react-doctor-score-100
|
CodeAnt AI is running Incremental review |
|
CodeAnt AI Incremental review completed. |
- app/page.tsx: replace browser-side useGetCompanies hook with server-side fetch using service role key to prevent silent empty companies array under RLS (api mismatch) - hooks/getExperiences.ts: add is_exclusive to new_interview select and map it to isExclusive in transformNewData so exclusive badges and admin stats work correctly (api mismatch) - app/api/pipeline/process/route.ts: add GET handler delegating to POST to preserve backward compat with cron/scheduler integrations (api mismatch) - app/api/seed-experiences/route.ts: switch Promise.all to Promise.allSettled so one failed upsert does not abort the entire batch (logic error) - app/api/summarize/route.ts: add SSRF protection — validate URL protocol and block private/loopback/metadata IP ranges before fetching (ssrf)
There was a problem hiding this comment.
Actionable comments posted: 19
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
components/ui/pagination.tsx (1)
43-60: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDo not give every page number the same accessible name.
Line 50 makes all numeric links from
QueryPaginationannounce as “Pagination link”. Derive the label from the visible page number or omit the fallback.Proposed fix
const PaginationLink = ({ className, isActive, size = 'icon', + children, + 'aria-label': ariaLabel, ...props }: PaginationLinkProps) => ( <a - aria-label={props['aria-label'] || 'Pagination link'} + aria-label={ + ariaLabel ?? + (typeof children === 'string' || typeof children === 'number' + ? `Go to page ${children}` + : undefined) + } aria-current={isActive ? 'page' : undefined} @@ )} {...props} - /> + > + {children} + </a> );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/ui/pagination.tsx` around lines 43 - 60, The PaginationLink component is giving every link the same accessible name via the aria-label fallback, which makes numeric page links indistinguishable. Update PaginationLink so its aria-label is derived from the visible page number passed through props, or remove the generic fallback altogether; keep the current aria-current behavior and preserve the existing PaginationLinkProps and QueryPagination usage so each page link announces a unique label.form/contexts/questions-context.tsx (1)
7-11: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMake the missing-provider guard actually fail.
QuestionsContexthas a concrete default value, souse(QuestionsContext)is never falsy and the check on Line 64 can never throw. A consumer rendered outsideQuestionsProviderwill silently get the noopdispatchand initial state instead of failing fast.Proposed fix
-const QuestionsContext = createContext<QuestionsContextType>({ - state: questionsInitialState, - dispatch: () => {}, - percent: 0, -}); +const QuestionsContext = createContext<QuestionsContextType | undefined>( + undefined +); ... export function useQuestions(): QuestionsContextType { const context = use(QuestionsContext); - - if (context) { - return context; - } - - throw new Error('useQuestions must be use inside QuestionsProvider'); + if (!context) { + throw new Error('useQuestions must be used inside QuestionsProvider'); + } + return context; }Also applies to: 62-68
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@form/contexts/questions-context.tsx` around lines 7 - 11, QuestionsContext currently has a concrete default value, so the missing-provider guard in useQuestionsContext() can never trigger and consumers outside QuestionsProvider silently get the fallback state/dispatch. Remove the non-null default from createContext<QuestionsContextType> and make useQuestionsContext() explicitly detect a missing provider and throw, then keep QuestionsProvider as the only place that supplies the real state, dispatch, and percent.app/terms/page.tsx (1)
17-23: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDon't render today's date as the terms revision date.
new Date()makes this page claim the Terms changed today even when the document did not.suppressHydrationWarningmasks the hydration warning but leaves the incorrect legal metadata in place. Please use a fixed last-reviewed/last-updated value.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/terms/page.tsx` around lines 17 - 23, The Terms page is rendering the current date via new Date() in the date label, which incorrectly implies the document was updated today. Replace this dynamic date formatting in app/terms/page.tsx with a fixed last-updated/last-reviewed value for the Terms revision, keeping the existing display structure but removing the dependence on the current system date.app/privacy/page.tsx (1)
17-23: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDon't render today's date as the policy revision date.
new Date()makes this page say the Privacy Policy was updated on every request.suppressHydrationWarningonly hides the mismatch; it doesn't fix the incorrect legal timestamp. Please render a fixed revision date from content or metadata instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/privacy/page.tsx` around lines 17 - 23, The PrivacyPolicy date rendering is using new Date() inside the page, which makes the policy appear updated on every request; replace this in app/privacy/page.tsx with a fixed revision date sourced from content or metadata instead of computing it at render time. Update the date logic in the privacy page component so the displayed “Last updated” value is stable and no longer depends on the current system date, and remove any reliance on suppressHydrationWarning for this timestamp.
🟡 Minor comments (13)
components/common/site-header.tsx-153-168 (1)
153-168: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAdd the missing mobile Captured admin link.
Desktop renders Admin/Captured/Ingest, but the mobile admin section only renders Admin/Ingest. The cohort expects the same admin destinations in mobile nav.
Proposed fix
<Link href='/admin' onClick={() => setOpen(false)} className='block text-lg font-medium text-orange-500 hover:text-primary mb-3' > Admin </Link> + <Link + href='/admin/captured' + onClick={() => setOpen(false)} + className='block text-lg font-medium text-orange-500 hover:text-primary mb-3' + > + Captured + </Link> <Link href='/admin/ingest' onClick={() => setOpen(false)}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/common/site-header.tsx` around lines 153 - 168, The mobile admin navigation in site-header.tsx is missing the Captured destination, so the admin menu on small screens does not match desktop. Update the isAdmin block in the mobile menu rendering to include the same Captured link alongside the existing Admin and Ingest links, using the same Link pattern and close-menu behavior as the other admin items.components/ui/alert.tsx-46-52 (1)
46-52: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAvoid overriding non-string heading content with a generic label.
When
childrenis JSX, Line 49 makes assistive tech announce “Alert title” instead of the visible title content. Prefer no fallbackaria-label, or require callers to pass one explicitly.Proposed fix
<h5 ref={ref} - aria-label={ - typeof props.children === 'string' ? props.children : 'Alert title' - } className={cn('mb-1 font-medium leading-none tracking-tight', className)} {...props} />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/ui/alert.tsx` around lines 46 - 52, Update the heading in AlertTitle so it does not force a generic aria-label when children is JSX; the current fallback in the h5 makes assistive tech ignore the visible title content. In the AlertTitle component, remove the automatic “Alert title” fallback and either leave aria-label unset for non-string children or only use an aria-label when the caller explicitly passes one. Keep the existing behavior for string children if needed, but ensure the label logic in AlertTitle does not override meaningful JSX content.components/ui/alert.tsx-43-66 (1)
43-66: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAlign the prop and ref types with the rendered elements.
AlertTitlerenders<h5>but declares a paragraph ref, andAlertDescriptionrenders a<div>while its props/ref are typed as paragraph elements. Update both to match the actual tags sorefand event targets are typed correctly.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/ui/alert.tsx` around lines 43 - 66, The prop and ref typings for AlertTitle and AlertDescription don’t match the elements they render, which can cause incorrect ref/event typing. Update AlertTitle to use heading element types for its props and ref since it renders an h5, and update AlertDescription to use div element types for its props and ref since it renders a div. Keep the component names and JSX structure the same while aligning the React.HTMLAttributes and React.Ref generics with the actual rendered tags.components/ui/card.tsx-38-56 (1)
38-56: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAlign
CardTitle’s ref type with its<h3>and drop the genericaria-label.refis typed asHTMLParagraphElementeven though the component renders a heading, and the fallback label replaces the real heading text for non-string children.components/ui/card.tsx:38-56🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/ui/card.tsx` around lines 38 - 56, The CardTitle component’s ref type is mismatched with the rendered h3, and its aria-label fallback is masking the actual heading content for non-string children. Update CardTitle so the ref type matches an HTMLHeadingElement (consistent with the h3 element) and remove the generic aria-label fallback, keeping the heading semantics tied to the rendered children; use the CardTitle symbol in components/ui/card.tsx to locate the change.components/motion-provider.tsx-3-4 (1)
3-4: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick winMount
MotionProviderin the app tree WireMotionProviderintoapp/layout.tsxfirst; the newm.*consumers aren’t under anyLazyMotionancestor yet, so they won’t get the intended feature bundle. Addstrictwhen you mount it to catch accidentalmotion.*regressions.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/motion-provider.tsx` around lines 3 - 4, MotionProvider is defined but not mounted high enough in the app tree, so the new m.* components are outside any LazyMotion ancestor and won’t receive the motion feature bundle. Update app/layout.tsx to wrap the app with MotionProvider, and mount it with strict enabled so MotionProvider catches accidental motion.* regressions while still providing domAnimation to descendants.form/components/date-selector/DateSelect.tsx-20-24 (1)
20-24: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep the internal ref attached when a parent passes
ref.
inputRefdrives the autofocus effect, but Line 31 stops assigning it as soon as an externalrefis provided. In that case the timeout never focuses the input, so the new ref API regresses the existing behavior.Proposed fix
-import { useEffect, useRef, ChangeEventHandler } from 'react'; +import { useEffect, useRef, ChangeEventHandler, useCallback } from 'react'; @@ const DateSelector = ({ className, value, onChange, placeholder, ref, }: DateSelectorProps) => { const inputRef = useRef<HTMLInputElement>(null); + const setRefs = useCallback( + (node: HTMLInputElement | null) => { + inputRef.current = node; + + if (typeof ref === 'function') { + ref(node); + } else if (ref) { + ref.current = node; + } + }, + [ref] + ); @@ - ref={ref ?? inputRef} + ref={setRefs}Also applies to: 31-31
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@form/components/date-selector/DateSelect.tsx` around lines 20 - 24, The autofocus logic in DateSelect depends on inputRef, but the ref assignment stops wiring that internal ref whenever an external ref is passed. Update DateSelect so the input element always keeps inputRef attached while also forwarding the parent ref, using the ref handling around inputRef and the component’s forwarded ref setup; this should preserve the existing timeout focus behavior for both internal and external refs.form/components/btn-container/BtnContainer.tsx-20-29 (1)
20-29: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winAvoid reading
navigator.userAgentduring the initial render.On the server this state is always
false, but on mobile clients it can hydrate astrue, so Line 47 can render different markup between SSR and the client. The resize listener does not fix that because the UA string never changes.Suggested fix
- const [isOnMobile, setIsOnMobile] = useState(() => - typeof navigator !== 'undefined' - ? navigator.userAgent.toLowerCase().includes('mobile') - : false - ); + const [isOnMobile, setIsOnMobile] = useState(false); useEffect(() => { - const handleResizeEvent = () => { - setIsOnMobile(navigator?.userAgent.toLowerCase().includes('mobile')); - }; + const updateIsOnMobile = () => { + setIsOnMobile(navigator.userAgent.toLowerCase().includes('mobile')); + }; + + updateIsOnMobile(); - window.addEventListener('resize', handleResizeEvent); + window.addEventListener('resize', updateIsOnMobile); return () => { - window.removeEventListener('resize', handleResizeEvent); + window.removeEventListener('resize', updateIsOnMobile); }; }, []);Also applies to: 47-47
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@form/components/btn-container/BtnContainer.tsx` around lines 20 - 29, The initial `useState` in `BtnContainer` is reading `navigator.userAgent` during render, which can cause SSR/client markup mismatches; change the `isOnMobile` initialization to a deterministic server-safe default and only evaluate the mobile UA inside `useEffect`/`handleResizeEvent` after mount. Keep the resize listener in sync by setting `isOnMobile` there, and rely on the post-mount update instead of using `navigator` in the initial render path.form/components/question-input-text/QuestionInputText.tsx-12-12 (1)
12-12: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep the internal autofocus ref wired up when
refis passed.Lines 25-29 always focus
inputTextRef, but Line 34 stops assigning the DOM node to that ref whenever a parent suppliesref. That makes the mount autofocus silently stop working for ref-backed usages.Suggested fix
type QuestionInputTextProps = { readonly placeholder?: string; readonly className?: string; readonly value?: string; readonly onChange?: ChangeEventHandler<HTMLInputElement>; readonly type?: string; ref?: React.Ref<HTMLInputElement>; }; const QuestionInputText = ({ placeholder, className, value, onChange, type, ref, }: QuestionInputTextProps) => { const inputTextRef = useRef<HTMLInputElement>(null); + const setRefs = (node: HTMLInputElement | null) => { + inputTextRef.current = node; + + if (typeof ref === 'function') { + ref(node); + } else if (ref) { + ref.current = node; + } + }; useEffect(() => { const id = setTimeout(() => { inputTextRef.current?.focus(); }, 500); return () => clearTimeout(id); }, []); return ( <input - ref={ref ?? inputTextRef} + ref={setRefs} aria-label={placeholder || 'Text input'}Also applies to: 21-35
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@form/components/question-input-text/QuestionInputText.tsx` at line 12, QuestionInputText currently drops its internal autofocus ref whenever a parent passes ref, so the mount focus logic stops working for ref-backed usages. Update the ref wiring in QuestionInputText so the DOM input is assigned to both the forwarded ref and the internal inputTextRef used by the autofocus effect, preserving focus behavior regardless of whether ref is provided.form/components/question-input-text/InterviewInputText.tsx-16-39 (1)
16-39: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winWire the ref into
TextEditor
form/components/question-input-text/InterviewInputText.tsx:27-39—textAreaRefis never attached to the rendered editor, so the delayedfocus()call is a no-op, and therefprop is unused. Forward the ref to the editor or remove the autofocus path if this input shouldn’t auto-focus.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@form/components/question-input-text/InterviewInputText.tsx` around lines 16 - 39, `InterviewInputText` defines `textAreaRef` and accepts a `ref` prop, but neither is connected to the rendered `TextEditor`, so the delayed focus effect never targets the actual input. Update `InterviewInputText` to forward the ref into `TextEditor` (or otherwise attach `textAreaRef` to the editor instance) so the autofocus logic can work, and remove the unused `ref`/focus path if `TextEditor` is not meant to support focusing.app/companies/[company]/page.tsx-189-189 (1)
189-189: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winEncode the
utm_campaignroute parameter.
companycomes from the URL path, so values containing&or=can alter the outbound query string. Encode it likecompanyName.Proposed fix
- href={`https://onlyfrontendjobs.com/jobs?q=${encodeURIComponent(companyName)}&utm_source=frontend-junction&utm_medium=company-page&utm_campaign=${company}`} + href={`https://onlyfrontendjobs.com/jobs?q=${encodeURIComponent(companyName)}&utm_source=frontend-junction&utm_medium=company-page&utm_campaign=${encodeURIComponent(company)}`}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/companies/`[company]/page.tsx at line 189, The outbound job link in the company page builds a query string using the route param `company` without encoding, which can corrupt the URL when it contains reserved characters. Update the `href` construction in `page.tsx` so `utm_campaign` uses the same encoding approach as `companyName` (for example, via `encodeURIComponent`) while keeping the rest of the link intact.form/components/question-input-industries/QuestionInputIndustries.tsx-42-47 (1)
42-47: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
optionClickedis dead in the new derived filter logic.Nothing in this component updates
optionClicked, so the Line 43 early-return path never runs and the selection state tied to that flag never activates after an industry is picked.Possible fix
- {filterIndustries.map((_industry) => ( + {filterIndustries.map((_industry) => ( <DropdownSelectOption key={_industry} - onClick={() => setLocalIndustry(_industry)} + onClick={() => { + setLocalIndustry(_industry); + setOptionClicked(true); + setShowIndustriesList(false); + }} isSelected={localIndustry === _industry && optionClicked} > {_industry} </DropdownSelectOption> ))}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@form/components/question-input-industries/QuestionInputIndustries.tsx` around lines 42 - 47, The derived filtering logic in QuestionInputIndustries is still checking optionClicked, but that state is never updated in this component so the early return is dead code. Remove the unused optionClicked branch from filterIndustries or wire it into the selection flow in QuestionInputIndustries so the picked industry state is actually reflected by the filter behavior.app/interview-experience/[slug]/page.tsx-137-153 (1)
137-153: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDon't infer the company from the first token of the title.
This will emit wrong FAQ schema for multi-word or prefixed titles (
Bank of America→Bank,The Trade Desk→The). Pull the company from structured experience data instead ofsplit(' ')[0].🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/interview-experience/`[slug]/page.tsx around lines 137 - 153, The FAQ generation in the interview experience page is incorrectly deriving the company name from experience.title using split(' ')[0], which breaks multi-word or prefixed company names. Update the company lookup in the same component to use the structured experience data source instead of parsing the title string, and then build the FAQ questions/answers from that reliable company field so the schema stays accurate.content/blog-post-2026-06-11/web-performance-in-2026-a-practical-guide.mdx-145-174 (1)
145-174: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
useMemostill reruns this filter on every keystroke.searchQueryis a dependency, so typing recomputesjobs.filter(...); the “no longer triggers any main-thread blocking” / “perfect INP” claim is too strong. Either soften the result text or show a real input-latency fix like debouncing,useDeferredValue, or virtualization.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@content/blog-post-2026-06-11/web-performance-in-2026-a-practical-guide.mdx` around lines 145 - 174, The `useMemo` example in the web performance post still recomputes `filteredJobs` on every `searchQuery` change, so the result text overstates the impact. Update the section around `filteredJobs` and the “The Result” copy to avoid claiming typing no longer blocks the main thread or guarantees perfect INP; instead, describe the improvement more conservatively or replace it with a true input-latency optimization such as debouncing, `useDeferredValue`, or virtualization.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/api/pipeline/process/route.ts`:
- Around line 39-43: The GET handler in GET should not delegate to POST because
it causes the full processing pipeline to run and mutate state on safe requests.
Remove the GET-to-POST passthrough in this route and keep the write path exposed
only through POST, leaving GET unimplemented or returning a method-not-allowed
response so accidental crawls or prefetches cannot trigger processing.
In `@app/api/pipeline/route.ts`:
- Around line 20-27: The authorization logic in the pipeline route currently
reads the cron secret from the query string, which exposes it in the URL. Update
the auth flow in the route handler to read the cron credential from a request
header instead of using searchParams/token from request.url, and keep the
existing hasValidToken/isAdmin/isAuthorized behavior intact. Use the route
handler’s request parsing and the getAuthState check as the main reference
points while removing any dependency on URL-based secret lookup.
- Around line 36-44: The fetch phase in the pipeline currently uses Promise.all,
so any single source failure aborts the entire ingest and returns a 500. Update
the source fetching logic in the pipeline route to use the same tolerant pattern
as the save phase, likely by switching the sourceResults aggregation in the
route handler to Promise.allSettled and filtering successful results from failed
ones. Keep the existing source.fetchArticles and logging behavior, but handle
rejected promises per source so one transient error does not stop other sources
from contributing articles.
In `@app/api/summarize/route.ts`:
- Around line 56-73: The hostname-only blocklist in the summarize route leaves a
DNS-resolution SSRF bypass, since a public hostname can still resolve to
loopback, metadata, or RFC1918/private IPs. Update the URL validation in the
summarize handler to resolve and inspect the destination address before the
fetch call, or switch to a strict allowlist of approved origins. Reuse the
existing parsedUrl / blockedPatterns check as the entry point, but add rejection
for any resolved private, link-local, loopback, or private IPv6 address before
calling fetch.
- Around line 73-74: The remote fetch in the summarize route currently waits on
fetch(url, { redirect: 'manual' }) and then buffers the full body with
response.text(), which can be abused with slow or oversized responses. Update
the route handler to enforce a hard request timeout and a maximum response body
size before parsing, using the existing fetch/response handling in the summarize
API path. Make sure the fix is applied around the response.text() flow so large
or hanging upstream responses are aborted instead of fully buffered.
In `@app/interview-experience/page.tsx`:
- Around line 37-50: The SSR fetch in interview-experience/page.tsx ignores the
page query param by hardcoding fetchPaginatedExperiences to page 1, so direct
URL loads do not match the requested state. Update the initial server-side fetch
to derive page from searchParams.page (with a safe default when absent/invalid)
and pass that value into fetchPaginatedExperiences alongside the existing
searchParams-driven filters so the server-rendered content matches the URL.
In `@app/page.tsx`:
- Around line 53-70: The getCompaniesServer helper is swallowing Supabase or
config failures and returning an empty array, which causes unstable_cache to
persist a blank result. Update getCompaniesServer to throw on errors instead of
returning [], and move the empty fallback handling outside the cached path where
getCompaniesServer is used. Keep the mapping logic intact, but ensure the cached
function only caches successful company data and not the fallback.
In `@components/add-new-experience.tsx`:
- Line 87: The submission mode in AddNewExperience is currently stored in
isExclusive as a ref, which prevents InterviewExperienceForm from re-rendering
when the user switches between “Quick Link” and “Write Exclusive”. Move this
mode to component state in AddNewExperience, keep it synchronized with the tab
change handler used by Tabs, and pass the state-derived value through to
InterviewExperienceForm so any mode-dependent validation and errors refresh
correctly.
In `@components/common/confetti.tsx`:
- Around line 15-22: The timeout in confetti.tsx is being created during render,
which can schedule multiple redirects and behaves badly in Strict Mode. Move the
redirect logic into a useEffect tied to the confetti modal lifecycle, and return
a cleanup function that clears the timeout. Keep the router.push('/') call
inside that effect, and remove the unused showModal.current state mutation
unless it is wired to actual UI state.
In `@components/common/site-header.tsx`:
- Around line 171-187: The mobile sheet in site-header.tsx is wrapping both
Profile and Login inside an extra button, creating nested interactive elements.
Update the mobile-only branch in the header render so Profile and Login are
rendered directly with the open-close behavior moved to the existing interactive
controls or a non-button container, keeping the user/menu click and keyboard
handling intact without nesting buttons.
In `@components/experiences.tsx`:
- Line 79: The pagination flow in the experiences view is vulnerable to stale
filter and observer races because `pageRef` can advance before the new filter
set has loaded and `isLoadingMore` may not update fast enough. Update the
pagination logic around `useRef`, `fetchMoreData`, and the filter-change
handling so pagination is reset/disabled immediately when filters change, and
add a synchronous in-flight or stale-request guard inside `fetchMoreData` to
ignore duplicate observer callbacks and prevent loading page N+1 with outdated
state.
In `@components/hero-section.tsx`:
- Around line 122-129: Remove the nested interactive element in the hero CTA by
updating the Link usage in hero-section.tsx around the Browse Experiences
action; Link already provides the clickable element, so replace the inner button
with Link styling or switch to a standalone button that navigates
programmatically. Keep the existing CTA classes/appearance on the Link itself
and ensure the component renders only one interactive control for this action.
In `@components/session-provider.tsx`:
- Around line 50-52: The session profile role is being populated from
user_metadata, which is user-controlled and blocks the users.user_role fallback
in resolveProfile(); update SessionProvider and resolveProfile so role is
derived only from user.app_metadata and the DB-backed users.user_role lookup,
and remove the user_metadata role source from the profile shape used for
admin/UI hints.
- Around line 101-104: The session/profile update flow in session-provider’s
initial load and onAuthStateChange can apply stale resolveProfile() results
after session changes. Update the logic around setSession, resolveProfile, and
setUser in SessionProvider to track the latest request or user id and only
commit the profile if it still matches the current session; discard older
completions so a sign-out or account switch cannot restore a previous user.
In `@doctor.config.json`:
- Around line 9-20: The repo-wide suppression in doctor.config.json is too broad
for the secret-boundary checks. Keep react-doctor/build-pipeline-secret-boundary
and react-doctor/artifact-baas-authority-surface enabled globally, and move any
necessary disablement to the specific server-side files that truly need
SUPABASE_SERVICE_ROLE_KEY access. Use the existing rules array in
doctor.config.json to narrow the exception scope and add a clear justification
wherever the suppression is applied.
In `@form/components/question-input-industries/QuestionInputIndustries.tsx`:
- Around line 112-118: The wrapper in QuestionInputIndustries is handling Enter
on a non-focusable div, which makes keyboard behavior unclear and can hijack
child interactions. Update the dropdown wrapper so keyboard activation is
explicit only if the wrapper is meant to be interactive, or remove the Enter
handler and keep activation on the actual focusable control instead; use the
existing handleDropdownClick logic only from the element that should own the
action.
In `@lib/content-pipeline/sources/devto.ts`:
- Around line 13-23: The Dev.to tag fetch aggregation in the tagResults
Promise.all block should not let one failed request cancel the entire batch.
Update the async mapper so each tag request is caught and converted into an
empty result (or equivalent fallback) while successful responses still return
their JSON, preserving partial results from the devto source. Use the existing
Promise.all flow in devto.ts and keep the behavior localized to the tag fetch
logic.
In `@lib/supabase.ts`:
- Around line 1-6: `lib/supabase.ts` is now using the browser client, but it is
still imported from server-side entry points like `actions/experiences.ts` and
`app/api/blog/view/[...slug]/route.ts`, so auth cookies may be lost. Update the
server-facing usages to import a server-safe Supabase helper that reads request
cookies, and keep `supabase`/`createClient` in `lib/supabase.ts` or
`@/utils/supabase/client` limited to browser code only. Use the existing symbols
`supabase`, `createClient`, and the affected server actions/routes to separate
browser and server helpers cleanly.
In `@next.config.mjs`:
- Around line 45-47: The Content-Security-Policy-Report-Only header currently
blocks YouTube iframes because frame-src only allows self, which conflicts with
the new YouTube embed support. Update the CSP definition in next.config.mjs to
add the required YouTube iframe host(s) to frame-src while preserving the
existing self restriction and other directives, so the policy matches the app’s
embed behavior and can later be enforced without violations.
---
Outside diff comments:
In `@app/privacy/page.tsx`:
- Around line 17-23: The PrivacyPolicy date rendering is using new Date() inside
the page, which makes the policy appear updated on every request; replace this
in app/privacy/page.tsx with a fixed revision date sourced from content or
metadata instead of computing it at render time. Update the date logic in the
privacy page component so the displayed “Last updated” value is stable and no
longer depends on the current system date, and remove any reliance on
suppressHydrationWarning for this timestamp.
In `@app/terms/page.tsx`:
- Around line 17-23: The Terms page is rendering the current date via new Date()
in the date label, which incorrectly implies the document was updated today.
Replace this dynamic date formatting in app/terms/page.tsx with a fixed
last-updated/last-reviewed value for the Terms revision, keeping the existing
display structure but removing the dependence on the current system date.
In `@components/ui/pagination.tsx`:
- Around line 43-60: The PaginationLink component is giving every link the same
accessible name via the aria-label fallback, which makes numeric page links
indistinguishable. Update PaginationLink so its aria-label is derived from the
visible page number passed through props, or remove the generic fallback
altogether; keep the current aria-current behavior and preserve the existing
PaginationLinkProps and QueryPagination usage so each page link announces a
unique label.
In `@form/contexts/questions-context.tsx`:
- Around line 7-11: QuestionsContext currently has a concrete default value, so
the missing-provider guard in useQuestionsContext() can never trigger and
consumers outside QuestionsProvider silently get the fallback state/dispatch.
Remove the non-null default from createContext<QuestionsContextType> and make
useQuestionsContext() explicitly detect a missing provider and throw, then keep
QuestionsProvider as the only place that supplies the real state, dispatch, and
percent.
---
Minor comments:
In `@app/companies/`[company]/page.tsx:
- Line 189: The outbound job link in the company page builds a query string
using the route param `company` without encoding, which can corrupt the URL when
it contains reserved characters. Update the `href` construction in `page.tsx` so
`utm_campaign` uses the same encoding approach as `companyName` (for example,
via `encodeURIComponent`) while keeping the rest of the link intact.
In `@app/interview-experience/`[slug]/page.tsx:
- Around line 137-153: The FAQ generation in the interview experience page is
incorrectly deriving the company name from experience.title using split(' ')[0],
which breaks multi-word or prefixed company names. Update the company lookup in
the same component to use the structured experience data source instead of
parsing the title string, and then build the FAQ questions/answers from that
reliable company field so the schema stays accurate.
In `@components/common/site-header.tsx`:
- Around line 153-168: The mobile admin navigation in site-header.tsx is missing
the Captured destination, so the admin menu on small screens does not match
desktop. Update the isAdmin block in the mobile menu rendering to include the
same Captured link alongside the existing Admin and Ingest links, using the same
Link pattern and close-menu behavior as the other admin items.
In `@components/motion-provider.tsx`:
- Around line 3-4: MotionProvider is defined but not mounted high enough in the
app tree, so the new m.* components are outside any LazyMotion ancestor and
won’t receive the motion feature bundle. Update app/layout.tsx to wrap the app
with MotionProvider, and mount it with strict enabled so MotionProvider catches
accidental motion.* regressions while still providing domAnimation to
descendants.
In `@components/ui/alert.tsx`:
- Around line 46-52: Update the heading in AlertTitle so it does not force a
generic aria-label when children is JSX; the current fallback in the h5 makes
assistive tech ignore the visible title content. In the AlertTitle component,
remove the automatic “Alert title” fallback and either leave aria-label unset
for non-string children or only use an aria-label when the caller explicitly
passes one. Keep the existing behavior for string children if needed, but ensure
the label logic in AlertTitle does not override meaningful JSX content.
- Around line 43-66: The prop and ref typings for AlertTitle and
AlertDescription don’t match the elements they render, which can cause incorrect
ref/event typing. Update AlertTitle to use heading element types for its props
and ref since it renders an h5, and update AlertDescription to use div element
types for its props and ref since it renders a div. Keep the component names and
JSX structure the same while aligning the React.HTMLAttributes and React.Ref
generics with the actual rendered tags.
In `@components/ui/card.tsx`:
- Around line 38-56: The CardTitle component’s ref type is mismatched with the
rendered h3, and its aria-label fallback is masking the actual heading content
for non-string children. Update CardTitle so the ref type matches an
HTMLHeadingElement (consistent with the h3 element) and remove the generic
aria-label fallback, keeping the heading semantics tied to the rendered
children; use the CardTitle symbol in components/ui/card.tsx to locate the
change.
In `@content/blog-post-2026-06-11/web-performance-in-2026-a-practical-guide.mdx`:
- Around line 145-174: The `useMemo` example in the web performance post still
recomputes `filteredJobs` on every `searchQuery` change, so the result text
overstates the impact. Update the section around `filteredJobs` and the “The
Result” copy to avoid claiming typing no longer blocks the main thread or
guarantees perfect INP; instead, describe the improvement more conservatively or
replace it with a true input-latency optimization such as debouncing,
`useDeferredValue`, or virtualization.
In `@form/components/btn-container/BtnContainer.tsx`:
- Around line 20-29: The initial `useState` in `BtnContainer` is reading
`navigator.userAgent` during render, which can cause SSR/client markup
mismatches; change the `isOnMobile` initialization to a deterministic
server-safe default and only evaluate the mobile UA inside
`useEffect`/`handleResizeEvent` after mount. Keep the resize listener in sync by
setting `isOnMobile` there, and rely on the post-mount update instead of using
`navigator` in the initial render path.
In `@form/components/date-selector/DateSelect.tsx`:
- Around line 20-24: The autofocus logic in DateSelect depends on inputRef, but
the ref assignment stops wiring that internal ref whenever an external ref is
passed. Update DateSelect so the input element always keeps inputRef attached
while also forwarding the parent ref, using the ref handling around inputRef and
the component’s forwarded ref setup; this should preserve the existing timeout
focus behavior for both internal and external refs.
In `@form/components/question-input-industries/QuestionInputIndustries.tsx`:
- Around line 42-47: The derived filtering logic in QuestionInputIndustries is
still checking optionClicked, but that state is never updated in this component
so the early return is dead code. Remove the unused optionClicked branch from
filterIndustries or wire it into the selection flow in QuestionInputIndustries
so the picked industry state is actually reflected by the filter behavior.
In `@form/components/question-input-text/InterviewInputText.tsx`:
- Around line 16-39: `InterviewInputText` defines `textAreaRef` and accepts a
`ref` prop, but neither is connected to the rendered `TextEditor`, so the
delayed focus effect never targets the actual input. Update `InterviewInputText`
to forward the ref into `TextEditor` (or otherwise attach `textAreaRef` to the
editor instance) so the autofocus logic can work, and remove the unused
`ref`/focus path if `TextEditor` is not meant to support focusing.
In `@form/components/question-input-text/QuestionInputText.tsx`:
- Line 12: QuestionInputText currently drops its internal autofocus ref whenever
a parent passes ref, so the mount focus logic stops working for ref-backed
usages. Update the ref wiring in QuestionInputText so the DOM input is assigned
to both the forwarded ref and the internal inputTextRef used by the autofocus
effect, preserving focus behavior regardless of whether ref is provided.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d8e71521-4259-4dbb-b303-ff3645fd4b62
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (98)
.github/ISSUE_TEMPLATE/bug_report.md.github/ISSUE_TEMPLATE/feature_request.md.github/pull_request_template.mdCONTRIBUTING.mdREADME.mdapp/api/admin/stats/route.tsapp/api/interview/view/[id]/route.tsapp/api/og/route.tsxapp/api/pipeline/process/route.tsapp/api/pipeline/route.tsapp/api/seed-experiences/route.tsapp/api/summarize/route.tsapp/blog/page.tsxapp/companies/[company]/page.tsxapp/companies/page.tsxapp/error.tsxapp/interview-experience/[slug]/page.tsxapp/interview-experience/page.tsxapp/page.tsxapp/posts/[slug]/page.tsxapp/privacy/page.tsxapp/sitemap.tsapp/terms/page.tsxcomponents/add-new-experience.tsxcomponents/admin-dashboard.tsxcomponents/blog-cta-section.tsxcomponents/common/card.tsxcomponents/common/confetti.tsxcomponents/common/footer.tsxcomponents/common/loader.tsxcomponents/common/login.tsxcomponents/common/mobile-sticky-cta.tsxcomponents/common/site-header.tsxcomponents/common/text-editor.tsxcomponents/experiences.tsxcomponents/features-section.tsxcomponents/hero-section.tsxcomponents/home.tsxcomponents/motion-provider.tsxcomponents/query-pagination.tsxcomponents/session-provider.tsxcomponents/share-buttons.tsxcomponents/stats-section.tsxcomponents/structured-data.tsxcomponents/ui/alert.tsxcomponents/ui/button.tsxcomponents/ui/card.tsxcomponents/ui/dialog.tsxcomponents/ui/pagination.tsxcomponents/ui/select.tsxcomponents/ui/sheet.tsxcomponents/ui/tabs.tsxcomponents/ui/toast.tsxcomponents/ui/tooltip.tsxcomponents/view-counter.tsxcomponents/youtube.tsxcontent/blog-post-2026-06-09/interview-questions-in-2026-a-practical-guide.mdxcontent/blog-post-2026-06-11/web-performance-in-2026-a-practical-guide.mdxdocs/auth-standardization-spec.mddoctor.config.jsonform/components/btn-container/BtnContainer.tsxform/components/date-selector/DateSelect.tsxform/components/dropdown-select-option/DropdownSelectOption.tsxform/components/main-content/MainContent.tsxform/components/question-input-industries/QuestionInputIndustries.tsxform/components/question-input-text/InterviewInputText.tsxform/components/question-input-text/QuestionInputText.tsxform/components/question-num-heading/QuestionNumHeading.tsxform/components/question/DateInput.tsxform/components/question/EmailInput.tsxform/components/question/FirstNameInput.tsxform/components/question/GoalInput.tsxform/components/question/IndustryInput.tsxform/components/question/InterviewInput.tsxform/components/question/Intro.tsxform/components/question/LastNameInput.tsxform/components/question/PostType.tsxform/components/question/Question.tsxform/components/question/RoleInput.tsxform/constants/GOALS.tsform/constants/ROLES.tsform/contexts/questions-context.tsxform/contexts/shared-states-context.tsxform/hooks/useHandleKeypress.tsform/hooks/useHandleScroll.tsform/hooks/useSubmit.tsform/reducers/reducer-func/questionsReducerFunc.tsform/types/contexts.tshooks/getExperiences.tshooks/useGetCompanies.tshooks/useGetStats.tslib/content-pipeline/sources/devto.tslib/content-pipeline/sources/medium.tslib/is-mobile.tslib/supabase-browser.tslib/supabase.tsnext.config.mjspackage.json
| export async function GET(request: Request) { | ||
| return POST(request); | ||
| } | ||
|
|
||
| export async function POST(request: Request) { |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Keep this write path POST-only.
Line 40 makes a GET request execute the full processing pipeline and write back to the database. That breaks HTTP safety and makes accidental crawls, previews, or prefetches capable of triggering mutations.
Proposed fix
export async function GET(request: Request) {
- return POST(request);
+ return NextResponse.json({ error: 'Method not allowed' }, { status: 405 });
}📝 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.
| export async function GET(request: Request) { | |
| return POST(request); | |
| } | |
| export async function POST(request: Request) { | |
| export async function GET(request: Request) { | |
| return NextResponse.json({ error: 'Method not allowed' }, { status: 405 }); | |
| } | |
| export async function POST(request: Request) { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/api/pipeline/process/route.ts` around lines 39 - 43, The GET handler in
GET should not delegate to POST because it causes the full processing pipeline
to run and mutate state on safe requests. Remove the GET-to-POST passthrough in
this route and keep the write path exposed only through POST, leaving GET
unimplemented or returning a method-not-allowed response so accidental crawls or
prefetches cannot trigger processing.
| // Authorized by a valid cron token OR an admin session (consistent rule). | ||
| const { searchParams } = new URL(request.url); | ||
| const token = searchParams.get('token'); | ||
| const cronSecret = process.env.CRON_SECRET; | ||
|
|
||
| const cookieStore = await cookies(); | ||
| const supabase = createServerClient( | ||
| process.env.NEXT_PUBLIC_SUPABASE_URL!, | ||
| process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, | ||
| { | ||
| cookies: { | ||
| get(name: string) { | ||
| return cookieStore.get(name)?.value; | ||
| }, | ||
| }, | ||
| } | ||
| ); | ||
|
|
||
| const { | ||
| data: { session }, | ||
| } = await supabase.auth.getSession(); | ||
| const isAdmin = session?.user.email === 'deepaksharma834@gmail.com'; | ||
|
|
||
| const isAuthorized = (cronSecret && token === cronSecret) || isAdmin; | ||
| const hasValidToken = Boolean(cronSecret && token === cronSecret); | ||
| const isAdmin = hasValidToken ? false : (await getAuthState()).isAdmin; | ||
| const isAuthorized = hasValidToken || isAdmin; |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Move the cron secret out of the query string.
Reading token from request.url leaks the credential into logs, tracing, browser history, and intermediary caches. Accept it from a header instead so the endpoint URL never carries secrets.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/api/pipeline/route.ts` around lines 20 - 27, The authorization logic in
the pipeline route currently reads the cron secret from the query string, which
exposes it in the URL. Update the auth flow in the route handler to read the
cron credential from a request header instead of using searchParams/token from
request.url, and keep the existing hasValidToken/isAdmin/isAuthorized behavior
intact. Use the route handler’s request parsing and the getAuthState check as
the main reference points while removing any dependency on URL-based secret
lookup.
| // 1. Fetch from all sources in parallel | ||
| const sourceResults = await Promise.all( | ||
| sources.map(async (source) => { | ||
| console.log(`[Pipeline] Fetching from ${source.name}...`); | ||
| const articles = await source.fetchArticles(50); | ||
| console.log(`[Pipeline] Got ${articles.length} from ${source.name}`); | ||
| return articles; | ||
| }) | ||
| ); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Don’t let one source failure abort the whole ingest.
Promise.all here turns any transient source error into a full 500, even when the other sources succeeded. This pipeline already tolerates partial failures during the save phase, so the fetch phase should use the same allSettled pattern.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/api/pipeline/route.ts` around lines 36 - 44, The fetch phase in the
pipeline currently uses Promise.all, so any single source failure aborts the
entire ingest and returns a 500. Update the source fetching logic in the
pipeline route to use the same tolerant pattern as the save phase, likely by
switching the sourceResults aggregation in the route handler to
Promise.allSettled and filtering successful results from failed ones. Keep the
existing source.fetchArticles and logging behavior, but handle rejected promises
per source so one transient error does not stop other sources from contributing
articles.
| // Block private/loopback/metadata ranges | ||
| const hostname = parsedUrl.hostname.toLowerCase(); | ||
| const blockedPatterns = [ | ||
| /^localhost$/, | ||
| /^127\./, | ||
| /^10\./, | ||
| /^172\.(1[6-9]|2\d|3[01])\./, | ||
| /^192\.168\./, | ||
| /^169\.254\./, // AWS/GCP/Azure instance metadata | ||
| /^::1$/, | ||
| /^0\.0\.0\.0$/, | ||
| ]; | ||
| if (blockedPatterns.some((re) => re.test(hostname))) { | ||
| return NextResponse.json({ error: 'URL not allowed' }, { status: 400 }); | ||
| } | ||
|
|
||
| // 1. Fetch the URL content | ||
| const response = await fetch(url); | ||
| const response = await fetch(url, { redirect: 'manual' }); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Close the DNS-resolution SSRF bypass.
The blocklist only checks the hostname string. An attacker-controlled public hostname or CNAME that resolves to 127.0.0.1, 169.254.169.254, RFC1918, or private IPv6 can still be fetched server-side. Enforce a real allowlist, or resolve/pin the destination and reject private/link-local/loopback addresses before fetching.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/api/summarize/route.ts` around lines 56 - 73, The hostname-only blocklist
in the summarize route leaves a DNS-resolution SSRF bypass, since a public
hostname can still resolve to loopback, metadata, or RFC1918/private IPs. Update
the URL validation in the summarize handler to resolve and inspect the
destination address before the fetch call, or switch to a strict allowlist of
approved origins. Reuse the existing parsedUrl / blockedPatterns check as the
entry point, but add rejection for any resolved private, link-local, loopback,
or private IPv6 address before calling fetch.
| const response = await fetch(url, { redirect: 'manual' }); | ||
| const html = await response.text(); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Cap the remote fetch duration and body size.
response.text() buffers the entire remote body before the later 10k-character slice, so an authenticated user can point this route at a slow or very large response and tie up memory/request capacity. Add a timeout and stream-size limit before parsing.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/api/summarize/route.ts` around lines 73 - 74, The remote fetch in the
summarize route currently waits on fetch(url, { redirect: 'manual' }) and then
buffers the full body with response.text(), which can be abused with slow or
oversized responses. Update the route handler to enforce a hard request timeout
and a maximum response body size before parsing, using the existing
fetch/response handling in the summarize API path. Make sure the fix is applied
around the response.text() flow so large or hanging upstream responses are
aborted instead of fully buffered.
| "rules": [ | ||
| "react-doctor/build-pipeline-secret-boundary", | ||
| "react-doctor/artifact-baas-authority-surface", | ||
| "react-doctor/no-react19-deprecated-apis", | ||
| "react-doctor/no-multi-comp", | ||
| "react-doctor/only-export-components", | ||
| "react-doctor/heading-has-content", | ||
| "react-doctor/no-giant-component", | ||
| "react-doctor/prefer-useReducer", | ||
| "react-doctor/no-pass-data-to-parent", | ||
| "react-doctor/no-prop-callback-in-effect" | ||
| ] |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
sed -n '1,120p' doctor.config.json
echo
sed -n '1,220p' app/page.tsx
echo
rg -n 'SUPABASE_SERVICE_ROLE_KEY|service-role|service role' app lib componentsRepository: deepu0/frontend-junction
Length of output: 6146
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- doctor.config.json ---'
cat -n doctor.config.json
printf '\n%s\n' '--- react-doctor references ---'
rg -n "react-doctor|build-pipeline-secret-boundary|artifact-baas-authority-surface" -S .
printf '\n%s\n' '--- service-role usages ---'
rg -n "SUPABASE_SERVICE_ROLE_KEY|service-role|service role" app lib components -SRepository: deepu0/frontend-junction
Length of output: 4781
🌐 Web query:
react-doctor config ignore rules global semantics build-pipeline-secret-boundary artifact-baas-authority-surface
💡 Result:
The terms you listed refer to distinct concepts in React development, security tooling, and agent-based workflows. React Doctor Configuration and Ignoring Rules React Doctor is a diagnostic tool for React projects [1]. You can manage its behavior using a react-doctor.config.json or doctor.config.ts file [2][1]. To ignore specific rules or files, use the ignore object in your configuration [3][4][2]: ignore: { rules: ["react-doctor/no-danger"], files: ["src/generated/"], overrides: [ { files: ["components/search/HighlightedSnippet.tsx"], rules: ["react-doctor/no-danger"] } ] } You can also use inline suppressions, such as // react-doctor-disable-next-line , for specific lines of code [3]. artifact-baas-authority-surface This is a specific security rule (react-doctor/artifact-baas-authority-surface) within React Doctor [5]. It flags the inclusion of Backend-as-a-Service (BaaS) client configurations—such as those for Firebase or Supabase—within a browser bundle [5]. Exposing collection and authorization-field names in the client bundle can provide attackers with a map of your data model, especially if server-side rules (like Row-Level Security) are not properly configured to restrict access [5]. global semantics / build-pipeline-secret-boundary These terms relate to secure CI/CD architecture rather than a single specific configuration flag [6][7]. - Build-pipeline-secret-boundary refers to the practice of isolating secrets within CI/CD pipelines [7][8]. Modern best practices involve using ephemeral identities (e.g., OIDC) rather than long-lived API tokens, and ensuring that secrets are only accessible to specific, authorized build steps (e.g., via availableSecrets or secretEnv in Cloud Build) [6][7][8][9]. - Global semantics typically refers to the enforcement of centralized, organization-wide policies—such as security constraints—that must be respected by individual build pipelines to ensure consistent compliance across isolated environments [7]. surface / agent workflows The term surface refers to an agent-driven pattern for ephemeral, interactive UIs [10][11]. Unlike static Artifacts, a surface allows an AI agent to generate a structured web page at a URL, deliver that link, and autonomously process interactions (submissions) without requiring the user to return to the chat interface [10][11]. This pattern is designed for tasks that require complex UI affordances (like ordering lists or reviewing annotated plans) that are difficult to manage through standard chat text [10][11].
Citations:
- 1: https://github.com/millionco/react-doctor
- 2: https://www.mintlify.com/millionco/react-doctor/usage/configuration
- 3: https://www.react.doctor/docs/configuration/rules-ignores-suppressions
- 4: https://www.react.doctor/docs/configuration/config-files
- 5: https://www.react.doctor/docs/rules
- 6: https://sikkerkey.com/blog/ci-cd-secrets-management
- 7: https://truly.cloud/ci-cd-pipelines-for-isolated-sovereign-environments
- 8: https://cloudwebschool.com/docs/gcp/devops-and-cicd/secrets-in-cicd-pipelines/
- 9: https://cloud.google.com/build/docs/securing-builds/use-secrets
- 10: https://github.com/aac/surface
- 11: https://aac.social/posts/enhancing-the-effectiveness-of-html/
Keep the secret-boundary rules enabled.
doctor.config.json disables react-doctor/build-pipeline-secret-boundary and react-doctor/artifact-baas-authority-surface for the whole repo. This codebase does use SUPABASE_SERVICE_ROLE_KEY in server-side paths, so these checks are the guardrails for privileged access; narrow any suppression to the specific files that need it and add a justification.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@doctor.config.json` around lines 9 - 20, The repo-wide suppression in
doctor.config.json is too broad for the secret-boundary checks. Keep
react-doctor/build-pipeline-secret-boundary and
react-doctor/artifact-baas-authority-surface enabled globally, and move any
necessary disablement to the specific server-side files that truly need
SUPABASE_SERVICE_ROLE_KEY access. Use the existing rules array in
doctor.config.json to narrow the exception scope and add a clear justification
wherever the suppression is applied.
| <div | ||
| className={styles['dropdown-select__industries']} | ||
| role='group' | ||
| onClick={handleDropdownClick} | ||
| onKeyDown={(e) => { | ||
| if (e.key === 'Enter') handleDropdownClick(e as unknown as MouseEvent); | ||
| }} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Don’t trap Enter on the non-focusable wrapper.
role='group' does not make this div keyboard-focusable. As written, this handler only catches bubbled Enter presses from the inner input/button, and handleDropdownClick() stops propagation, so Enter gets hijacked instead of making the wrapper accessible.
Safer direction
<div
className={styles['dropdown-select__industries']}
role='group'
onClick={handleDropdownClick}
- onKeyDown={(e) => {
- if (e.key === 'Enter') handleDropdownClick(e as unknown as MouseEvent);
- }}
>If you want the wrapper itself to be keyboard-activatable, make that explicit instead of relying on bubbled child events.
+ tabIndex={0}
+ onKeyDown={(e) => {
+ if (e.currentTarget === e.target && (e.key === 'Enter' || e.key === ' ')) {
+ e.preventDefault();
+ setShowIndustriesList(true);
+ }
+ }}📝 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.
| <div | |
| className={styles['dropdown-select__industries']} | |
| role='group' | |
| onClick={handleDropdownClick} | |
| onKeyDown={(e) => { | |
| if (e.key === 'Enter') handleDropdownClick(e as unknown as MouseEvent); | |
| }} | |
| <div | |
| className={styles['dropdown-select__industries']} | |
| role='group' | |
| onClick={handleDropdownClick} | |
| /> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@form/components/question-input-industries/QuestionInputIndustries.tsx` around
lines 112 - 118, The wrapper in QuestionInputIndustries is handling Enter on a
non-focusable div, which makes keyboard behavior unclear and can hijack child
interactions. Update the dropdown wrapper so keyboard activation is explicit
only if the wrapper is meant to be interactive, or remove the Enter handler and
keep activation on the actual focusable control instead; use the existing
handleDropdownClick logic only from the element that should own the action.
| const tagResults = await Promise.all( | ||
| tags.map(async (tag) => { | ||
| const response = await fetch( | ||
| `https://dev.to/api/articles?tag=${tag}&per_page=${Math.ceil(limit / tags.length)}` | ||
| ); | ||
| if (response.ok) { | ||
| return response.json(); | ||
| } | ||
| return []; | ||
| }) | ||
| ); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Preserve partial results when one Dev.to request fails.
A single rejected fetch() now rejects the whole Promise.all, so one transient tag failure drops the entire Dev.to batch instead of keeping the successful tags.
Suggested fix
- const tagResults = await Promise.all(
+ const tagResults = await Promise.all(
tags.map(async (tag) => {
- const response = await fetch(
- `https://dev.to/api/articles?tag=${tag}&per_page=${Math.ceil(limit / tags.length)}`
- );
- if (response.ok) {
- return response.json();
- }
- return [];
+ try {
+ const response = await fetch(
+ `https://dev.to/api/articles?tag=${tag}&per_page=${Math.ceil(limit / tags.length)}`
+ );
+ if (!response.ok) return [];
+ return await response.json();
+ } catch (error) {
+ console.warn(`Failed to fetch Dev.to tag: ${tag}`, error);
+ 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.
| const tagResults = await Promise.all( | |
| tags.map(async (tag) => { | |
| const response = await fetch( | |
| `https://dev.to/api/articles?tag=${tag}&per_page=${Math.ceil(limit / tags.length)}` | |
| ); | |
| if (response.ok) { | |
| return response.json(); | |
| } | |
| return []; | |
| }) | |
| ); | |
| const tagResults = await Promise.all( | |
| tags.map(async (tag) => { | |
| try { | |
| const response = await fetch( | |
| `https://dev.to/api/articles?tag=${tag}&per_page=${Math.ceil(limit / tags.length)}` | |
| ); | |
| if (!response.ok) return []; | |
| return await response.json(); | |
| } catch (error) { | |
| console.warn(`Failed to fetch Dev.to tag: ${tag}`, error); | |
| return []; | |
| } | |
| }) | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/content-pipeline/sources/devto.ts` around lines 13 - 23, The Dev.to tag
fetch aggregation in the tagResults Promise.all block should not let one failed
request cancel the entire batch. Update the async mapper so each tag request is
caught and converted into an empty result (or equivalent fallback) while
successful responses still return their JSON, preserving partial results from
the devto source. Use the existing Promise.all flow in devto.ts and keep the
behavior localized to the tag fetch logic.
| import { createClient } from '@/utils/supabase/client'; | ||
|
|
||
| /** | ||
| * NOTE: Administrative operations using SUPABASE_SERVICE_ROLE_KEY | ||
| * MUST only be performed in server-side code (API routes or Sever Actions). | ||
| * NEVER prefix service role keys with NEXT_PUBLIC_. | ||
| * @deprecated Use `createClient` from `@/utils/supabase/client` directly. | ||
| */ | ||
| export const supabase = createClient(); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Files ==\n'
git ls-files 'lib/supabase.ts' 'utils/supabase/client.*' 'utils/supabase/server.*' 'hooks/getExperiences.ts' 'app/api/interview/view/[id]/route.ts' 2>/dev/null || true
printf '\n== lib/supabase.ts ==\n'
cat -n lib/supabase.ts
printf '\n== utils/supabase/client.* ==\n'
for f in utils/supabase/client.ts utils/supabase/client.tsx utils/supabase/client.js; do
[ -f "$f" ] && { echo "--- $f"; cat -n "$f"; }
done
printf '\n== utils/supabase/server.* ==\n'
for f in utils/supabase/server.ts utils/supabase/server.tsx utils/supabase/server.js; do
[ -f "$f" ] && { echo "--- $f"; cat -n "$f"; }
done
printf '\n== server-side consumers ==\n'
for f in hooks/getExperiences.ts app/api/interview/view/[id]/route.ts; do
[ -f "$f" ] && { echo "--- $f"; rg -n "supabase|createClient|createBrowserClient|createServerClient|cookies|headers" "$f"; }
done
printf '\n== import references to lib/supabase ==\n'
rg -n "from ['\"]`@/lib/supabase`['\"]|from ['\"].*/lib/supabase['\"]|from ['\"]lib/supabase['\"]" .Repository: deepu0/frontend-junction
Length of output: 3347
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== hooks/getExperiences.ts ==\n'
cat -n hooks/getExperiences.ts
printf '\n== hooks/useGetCompanies.ts ==\n'
cat -n hooks/useGetCompanies.ts
printf '\n== form/hooks/useSubmit.ts ==\n'
cat -n form/hooks/useSubmit.ts
printf '\n== actions/experiences.ts ==\n'
cat -n actions/experiences.ts
printf '\n== app/api/blog/view/[...slug]/route.ts ==\n'
cat -n app/api/blog/view/[...slug]/route.ts
printf '\n== client/server usage hints ==\n'
rg -n "use client|use server|cookies\(|headers\(|createServerClient|createBrowserClient|from '`@/lib/supabase`'" hooks form actions app components libRepository: deepu0/frontend-junction
Length of output: 20700
Keep lib/supabase.ts on a server-safe client. lib/supabase.ts now wraps @/utils/supabase/client, but it’s still imported from server-side code such as actions/experiences.ts and app/api/blog/view/[...slug]/route.ts. A browser client there won’t carry request cookies, so auth/RLS can fall back to anon and return the wrong rows. Split the browser and server helpers instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/supabase.ts` around lines 1 - 6, `lib/supabase.ts` is now using the
browser client, but it is still imported from server-side entry points like
`actions/experiences.ts` and `app/api/blog/view/[...slug]/route.ts`, so auth
cookies may be lost. Update the server-facing usages to import a server-safe
Supabase helper that reads request cookies, and keep `supabase`/`createClient`
in `lib/supabase.ts` or `@/utils/supabase/client` limited to browser code only.
Use the existing symbols `supabase`, `createClient`, and the affected server
actions/routes to separate browser and server helpers cleanly.
| key: 'Content-Security-Policy-Report-Only', | ||
| value: | ||
| "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://googleads.g.doubleclick.net https://va.vercel-scripts.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data:; connect-src 'self' https://*.supabase.co https://www.google-analytics.com https://vitals.vercel-insights.com; frame-src 'self'; object-src 'none'; base-uri 'self'", |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Allow the app’s own YouTube embeds in the CSP policy.
frame-src 'self' contradicts the YouTube iframe support added in this same release, so this report-only CSP will flag your own embeds as violations and won’t be promotable to enforcement as-is.
Suggested policy adjustment
- "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://googleads.g.doubleclick.net https://va.vercel-scripts.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data:; connect-src 'self' https://*.supabase.co https://www.google-analytics.com https://vitals.vercel-insights.com; frame-src 'self'; object-src 'none'; base-uri 'self'",
+ "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://googleads.g.doubleclick.net https://va.vercel-scripts.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data:; connect-src 'self' https://*.supabase.co https://www.google-analytics.com https://vitals.vercel-insights.com; frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com; object-src 'none'; base-uri 'self'",📝 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.
| key: 'Content-Security-Policy-Report-Only', | |
| value: | |
| "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://googleads.g.doubleclick.net https://va.vercel-scripts.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data:; connect-src 'self' https://*.supabase.co https://www.google-analytics.com https://vitals.vercel-insights.com; frame-src 'self'; object-src 'none'; base-uri 'self'", | |
| key: 'Content-Security-Policy-Report-Only', | |
| value: | |
| "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://googleads.g.doubleclick.net https://va.vercel-scripts.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data:; connect-src 'self' https://*.supabase.co https://www.google-analytics.com https://vitals.vercel-insights.com; frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com; object-src 'none'; base-uri 'self'", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@next.config.mjs` around lines 45 - 47, The
Content-Security-Policy-Report-Only header currently blocks YouTube iframes
because frame-src only allows self, which conflicts with the new YouTube embed
support. Update the CSP definition in next.config.mjs to add the required
YouTube iframe host(s) to frame-src while preserving the existing self
restriction and other directives, so the policy matches the app’s embed behavior
and can later be enforced without violations.
|
CodeAnt AI is running Incremental review |
|
CodeAnt AI Incremental review completed. |
User description
Summary
Fixes all 225 issues (14 errors, 211 warnings) flagged by
npx react-doctor@latest. Final result: No issues found!What was fixed
Security
.replace(/</g, '\\u003c'))Performance
framer-motionimport withLazyMotion + macross 9 components (-30KB)mobile-device-detectwith a 3-line regex utility (-30KB)useRefinstead ofuseStatefor handler-only state (4 components)Promise.allfor sequential independent awaits (6 locations)Bugs
typeattributesetTimeoutcleanup (4 useEffect locations)next/headremoved from App Router pageuseSearchParamssuppressHydrationWarningfor date hydration mismatchesMaintainability
forwardRefremoved from all shadcn/ui components (React 19 pattern)useContext→usein context consumersAccessibility
role="button"divs → actual<button>elementsaria-labeladded to controlsrole="navigation"removed from<nav>Tested
npx react-doctor@latest→ No issues found! Build passes.Dependencies
Requires
fix/auth-standardizationto be merged first (importslib/auth.ts).Summary by CodeRabbit
CodeAnt-AI Description
Improve site performance, security, and admin access while fixing UI and form issues
What Changed
Impact
✅ Faster page loads on home, blog, and interview pages✅ Clearer admin access for authorized users✅ Fewer unsafe URL and script injection risks💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.