Skip to content

feat: redesign testimonial marquee with manual controls, add security,headers, and fix SEO issues#335

Closed
HEETMEHTA18 wants to merge 26 commits into
keploy:mainfrom
HEETMEHTA18:feat/testimonial-marquee-redesign
Closed

feat: redesign testimonial marquee with manual controls, add security,headers, and fix SEO issues#335
HEETMEHTA18 wants to merge 26 commits into
keploy:mainfrom
HEETMEHTA18:feat/testimonial-marquee-redesign

Conversation

@HEETMEHTA18

Copy link
Copy Markdown

Related Tickets & Documents

Fixes: #3440

Description

This PR revamps the blog landing page’s testimonial section to provide a "flawless" user experience, addressing community feedback regarding UI aesthetics and security. It replaces the legacy marquee with a modern dual-row scrolling layout.

GDG CHARUSAT TEAM ID: <Team 143>

Changes

  • Implemented a modern dual-row infinite scroll marquee for community testimonials.
  • Enhanced card design with quote icons, hover effects, and skeleton loaders.
  • Implemented core security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) in next.config.js.
  • Fixed relative paths for favicons and RSS feeds in meta.tsx to ensure they load correctly under the /blog base path.
  • Standardized SEO tokens and fixed variable typos in pages/index.tsx.

Type of Change

  • Chore (maintenance, refactoring, tooling updates)
  • Bug fix (non-breaking change that fixes an issue)
  • New feature (change that adds functionality)
  • Breaking Change (may require updates in existing code)
  • UI improvement (visual or design changes)
  • Performance improvement (optimization or efficiency enhancements)
  • Documentation update (changes to README, guides, etc.)
  • CI (updates to continuous integration workflows)
  • Revert (undo a previous commit or merge)

Testing

  • Performed local verification using npm run dev.
  • Verified the marquee functionality and edge-blending aesthetics.
  • Checked responsiveness on mobile and tablet views.
  • Ran npm run build to ensure no production regressions or hydration errors.

Demo

  • Testimonial Section: Now features dual-row scrolling with edge-blending gradients for a premium feel.
  • Security: Headers verified in browser dev tools.
  • SEO: Favicons and metadata links now correctly resolve to the /blog subdirectory.
    -Before:
Engineering._.Keploy.Blog.-.Google.Chrome.2026-02-24.21-44-31.mp4

After:

localhost_3000_blog.-.Google.Chrome.2026-02-24.21-51-45.mp4
image

Environment and Dependencies

  • New Dependencies: None.
  • Configuration Changes: Updated next.config.js to include standard security headers.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • I have added corresponding tests
  • I have run the build command to ensure there are no build errors
  • My changes have been tested across relevant browsers/devices
  • For UI changes, I've included visual evidence of my changes (Verified locally)

@kilo-code-bot

kilo-code-bot Bot commented Feb 24, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

This PR includes several quality improvements:

  • Security: Added security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) in next.config.js
  • Bug Fix: Fixed missing leading slashes in favicon/manifest paths in meta.tsx
  • Accessibility: Added aria-hidden="true" to decorative SVG icons in footer.tsx
  • Robustness: Added fallback handling for missing title and coverImage in cover-image.tsx
  • UX: Improved testimonials component with hydration-safe mounting, better avatar fallback handling via image proxy, and enhanced styling
  • Code Quality: Fixed typo allTehcnologyPostsallTechnologyPosts and removed duplicate <Head> tag in pages/index.tsx
Files Reviewed (6 files)
  • components/cover-image.tsx - Fallback handling improvements
  • components/footer.tsx - Accessibility improvements
  • components/meta.tsx - Path fixes
  • components/testimonials.tsx - Major refactor with improved UX
  • next.config.js - Security headers added
  • pages/index.tsx - Typo fix and duplicate removal

@HEETMEHTA18

Copy link
Copy Markdown
Author

hey @amaan-bhati i have proposed a solution to the given issue of the testimonial marquee which is redesigned to the design which you have mentioned in the issue

@dhananjay6561

Copy link
Copy Markdown
Member

Hi @HEETMEHTA18.
Thank you for contributing and taking the initiative to work on this!
However, the issue mentioned in this PR has already been fixed in the repository. Because of that, we won’t be able to proceed with merging this one.

Feel free to look into other open issues in the repository and pick one that hasn’t been addressed yet. We’d be happy to review another contribution from you. Thanks again for your effort!

@HEETMEHTA18

Copy link
Copy Markdown
Author

@dhananjay6561 however but still in the website no furthur changes are made till i just made changes into this so that the avatar was not loading but in the given pr the changes are made.
image

@dhananjay6561 dhananjay6561 requested a review from Copilot April 6, 2026 12:06
@dhananjay6561

Copy link
Copy Markdown
Member

Hey @HEETMEHTA18 👋 — thanks for putting this PR together, we appreciate the effort!

We've gone ahead and requested a Copilot review on this. Here's some context from the reviewer:

Too many unrelated changes — split into 2-3 PRs. Invalid transition-scale class. Title says 'manual controls' but none exist.

Once you've had a chance to go through the comments, please address the feedback and resolve the threads — and we'll get this across the line. Feel free to ask if anything's unclear. Happy coding! 💙

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR modernizes the blog landing page experience by redesigning the testimonials marquee, tightening security headers at the Next.js edge, and fixing SEO-related asset paths under the /blog basePath.

Changes:

  • Replaced the legacy testimonials marquee with a dual-row, hover-pausing infinite scroll layout (with a skeleton fallback during initial mount).
  • Added standard security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) via next.config.js.
  • Fixed SEO/meta asset URLs to be basePath-correct and cleaned up SEO token/typo issues on the homepage.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pages/index.tsx Removes inline <Head> title usage (relying on Layout/Meta) and fixes a typo in technology posts variable naming.
next.config.js Adds additional security headers alongside the existing CSP.
components/testimonials.tsx Implements the redesigned dual-row marquee testimonial UI and avatar proxy/fallback handling.
components/meta.tsx Corrects favicon/manifest/RSS/browserconfig paths to work under /blog basePath.
components/footer.tsx Adds aria-hidden to decorative social SVG icons.
components/cover-image.tsx Adds safer fallbacks for alt/src and refactors className handling (but currently introduces a critical build issue).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread components/cover-image.tsx Outdated
Comment on lines +26 to +28
className={cn("rounded-md transition-border duration-300", imgClassName, {
" transition-scale duration-300": slug,
})}

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

CoverImage now uses cn(...) but the helper isn’t imported in this file, which will cause a runtime/compile failure. Also, the Tailwind classes transition-border and transition-scale don’t exist in this repo’s Tailwind config, so the intended transitions won’t apply; use valid Tailwind transition utilities (or extend Tailwind) instead.

Suggested change
className={cn("rounded-md transition-border duration-300", imgClassName, {
" transition-scale duration-300": slug,
})}
className={[
"rounded-md transition-colors duration-300",
slug ? "transition-transform duration-300" : "",
imgClassName ?? "",
]
.filter(Boolean)
.join(" ")}

Copilot uses AI. Check for mistakes.
Comment on lines +20 to 26
/** Fallback avatar component – renders a user icon/initials in a branded circle */
const FallbackAvatar = ({ name }: { name: string }) => (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-slate-100 text-slate-500 ring-2 ring-gray-100 p-1">
<User className="h-full w-full opacity-60" />
</div>
);

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

getInitials is declared but never used, and FallbackAvatar accepts a name prop but doesn’t render initials (only the icon). This is dead code / misleading intent—either remove the unused helper/prop or render initials as the fallback so the behavior matches the comment.

Suggested change
/** Fallback avatar component – renders a user icon/initials in a branded circle */
const FallbackAvatar = ({ name }: { name: string }) => (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-slate-100 text-slate-500 ring-2 ring-gray-100 p-1">
<User className="h-full w-full opacity-60" />
</div>
);
/** Fallback avatar component – renders initials when available, otherwise a user icon */
const FallbackAvatar = ({ name }: { name: string }) => {
const initials = name.trim() ? getInitials(name) : "";
return (
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-slate-100 text-slate-500 ring-2 ring-gray-100 p-1">
{initials ? (
<span className="text-sm font-medium leading-none">{initials}</span>
) : (
<User className="h-full w-full opacity-60" />
)}
</div>
);
};

Copilot uses AI. Check for mistakes.
Comment thread pages/index.tsx
Comment on lines 26 to 36
return (

<Layout
preview={preview}
featuredImage={HOME_OG_IMAGE_URL}
Title={`Blog - Keploy`}
Description={"The Keploy Blog offers in-depth articles and expert insights on software testing, automation, and quality assurance, empowering developers to enhance their testing strategies and deliver robust applications."}
structuredData={structuredData}
>
<Head>
<title>{`Engineering | Keploy Blog`}</title>
</Head>
<Header />
<Container>

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

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

After removing the inline <Head> usage, the Head import at the top of this file is now unused and will be flagged by next lint. Please remove the unused import to keep CI/lint clean.

Copilot uses AI. Check for mistakes.

@amaan-bhati amaan-bhati left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

QA Report — feat: redesign testimonial marquee with manual controls, add security headers, and fix SEO issues

Repo: keploy/blog-website | Author: HEETMEHTA18 | Review date: 2026-04-30
Change type: content-rendering-change, config-change, metadata-or-seo-change


Summary

Severity Count
🔴 Blocking 3
🟡 Warning 4
🔵 Suggestion 4
⚪ Nitpick 3

Reviewed files: components/cover-image.tsx, components/footer.tsx, components/meta.tsx, components/testimonials.tsx, next.config.js, pages/index.tsx


Gate checklist

  • No blocking findings remain
  • Metadata contract still holds on production pages (see title warning)
  • Base-path and route integrity remain valid
  • Impacted files were checked for regressions

🔴 Blocking

  • components/cover-image.tsxcn used but never imported
    Why: The PR replaces the inline className template literal with cn(...) but adds no import for cn. This is a build-breaking ReferenceError — the site will not compile.
    Fix:

    import { cn } from "@/lib/utils"; // verify path under the @/* alias
  • components/testimonials.tsx:~87 — Raw <img> tag instead of next/image
    Why: Framework rule #3 blocks new raw <img> tags in component code. The new ReviewCard uses <img> for avatars, bypassing Next.js image optimization (format selection, lazy loading queue, responsive srcsets). The pages/api/proxy-image.ts proxy already exists, so next/image with the proxy URL is the correct pattern.
    Fix:

    import NextImage from "next/image";
    // …
    <NextImage
      className="rounded-full object-cover ring-2 ring-primary-100"
      width={40}
      height={40}
      alt={name ? `${name}'s avatar` : "Avatar"}
      src={proxiedAvatar}
      onError={() => setImgError(true)}
    />
  • components/cover-image.tsx:~32priority hardcoded to true for all cover images
    Why: priority={priority} (caller-controlled, defaulting to false) was replaced with the bare priority literal (always true). Every cover image sitewide — including list-page thumbnails — will now be eagerly preloaded, regressing Lighthouse CWV scores (TBT, FCP, TTI).
    Fix: Restore the conditional props:

    priority={priority}
    loading={priority ? "eager" : "lazy"}
    sizes={sizes}

🟡 Warnings

  • components/cover-image.tsxsizes prop silently dropped
    Why: sizes is still declared in the Props interface and destructured, but the new <Image> render no longer passes it. This silently removes the responsive image hint callers relied on for bandwidth-optimal image selection.
    Fix: Restore sizes={sizes} to the <Image> element, or remove sizes from the interface if it is intentionally deprecated.

  • components/meta.tsx:40-41Group.svg does not exist in public/favicon/
    Why: The fork's public/favicon/ directory contains safari-pinned-tab.svg but not Group.svg (confirmed by directory listing). The <link rel="mask-icon"> will 404 in Safari on every page globally. (Group.png does exist — the shortcut-icon change is fine.)
    Fix: Revert the mask-icon line:

    <link rel="mask-icon" href="/blog/favicon/safari-pinned-tab.svg" color="#000000" />

    …or add Group.svg to public/favicon/ before merging.

  • pages/index.tsx:36-38 — Route-specific <title> removed; title coverage unverified
    Why: The <Head><title>{Engineering | Keploy Blog}</title></Head> block is removed. The Meta component (reviewed in full) does not emit a <title> tag — only OG, Twitter, description, and canonical meta. If Layout does not emit <title> from the Title prop, the homepage will have no title, which is a critical SEO regression.
    Fix: Either restore the route-specific <title> (framework rule #2 explicitly permits this for pages/index.tsx), or document that layout.tsx emits <title> from the Title prop and verify it in the browser before merging.

  • components/testimonials.tsxuseRouter called per ReviewCard instance
    Why: ReviewCard is rendered for every tweet (20+ cards). useRouter() inside each card creates per-instance router subscriptions. The only value consumed — router.basePath — is a static build-time constant that never changes at runtime.
    Fix: Resolve basePath once in TwitterTestimonials and pass it as a prop to ReviewCard.


🔵 Suggestions

  • components/testimonials.tsx:13-17getInitials() is defined with a JSDoc comment but never called. FallbackAvatar shows the User icon instead. Either use getInitials(name) for initials display inside FallbackAvatar, or delete the function.

  • components/testimonials.tsx:56-62 — The useEffect that resets imgError on avatar prop change is unnecessary. Prop changes already trigger a re-render; the effect adds an extra render cycle with no real benefit. Remove it, or use key={avatar} on ReviewCard at the call site for a cleaner reset.

  • components/testimonials.tsx:115-145 — The mounted guard renders a skeleton on SSR and replaces it with the marquee on the client, causing measurable CLS. Consider using opacity-0 / visibility: hidden on the SSR pass and fading in on mount to avoid the layout shift.

  • next.config.jsX-Frame-Options: DENY is present but the CSP is missing frame-ancestors 'none'. Modern browsers prefer the CSP directive; adding both provides defense-in-depth for legacy and modern browsers alike.


⚪ Nitpicks

  • components/testimonials.tsx:7import { User } from "lucide-react" is placed after import Tweets from "../services/Tweets", breaking conventional import grouping (external packages before internal services). Move it with the other third-party imports at the top.

  • components/cover-image.tsx:35 — Double leading space in " transition-scale duration-300".

  • components/cover-image.tsx:34-36transition-border and transition-scale are not valid Tailwind CSS utilities. Standard Tailwind includes transition-colors and transition-transform; the non-existent classes will produce no CSS output.


✅ Looks Good

  • Security headers (X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy) are well-chosen and do not conflict with the existing CSP.
  • pages/index.tsx typo fix: allTehcnologyPostsallTechnologyPosts is a clean correction with no regressions.
  • components/footer.tsx accessibility: aria-hidden="true" on all four decorative SVG icons is correct — they are purely visual.
  • Marquee key uniqueness: Appending -r1 / -r2 to tweet IDs correctly prevents duplicate React key warnings.
  • Proxy routing: External avatar URLs routed through pages/api/proxy-image.ts follows the established allowlist pattern for this repo.
  • meta.tsx base-path correctness: All favicon hrefs retain the /blog/ prefix, consistent with basePath config.
  • Duplicate twitter:image cleaned up: The pre-featuredImage-conditional duplicate meta tag is removed; the consolidated structure is cleaner.

Cross-file impact

Direct findings:

  • components/cover-image.tsx — broken cn import blocks the build; priority regression affects all post-list and post-detail pages
  • components/testimonials.tsx — raw <img> bypasses the Next.js image pipeline for all testimonial avatars
  • components/meta.tsxGroup.svg 404 affects the Safari pinned-tab icon on every page (global)
  • pages/index.tsx — removed <title> may leave the homepage without a title if Layout does not cover it

Inferred impact risks checked:

  • components/layout.tsx — not in diff; Meta confirmed not to emit <title>; title coverage by layout.tsx requires manual verification
  • pages/api/proxy-image.ts — confirmed present in base repo; proxy routing in testimonials is safe

Overall verdict

Do not merge in current state. Three blockers must be resolved first: the missing cn import (build failure), the raw <img> tag (framework rule violation), and the hardcoded priority={true} regression (performance). Confirm the homepage <title> is still emitted and fix the Group.svg 404 before this ships. The security header additions, footer accessibility improvements, and typo fix are all clean — once the blockers are addressed this PR will be in good shape.


Review generated by Keploy QA Agent · 2026-04-30

Copilot AI review requested due to automatic review settings May 16, 2026 06:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@HEETMEHTA18 HEETMEHTA18 force-pushed the feat/testimonial-marquee-redesign branch from 23691d3 to 729fe03 Compare May 27, 2026 11:55
HEETMEHTA18 and others added 16 commits May 27, 2026 17:27
… headers, and fix SEO issues

Signed-off-by: Heet Mehta <heetmehta18125@gmail.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
… issues

Signed-off-by: Heet Mehta <heetmehta18125@gmail.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
Signed-off-by: Heet Mehta <heetmehta18125@gmail.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
* fix: broken testimonial avatars + marquee UX improvements

* fix: address Copilot review feedback on PR #330
* style: improve blog post typography, readability, and TOC scrolling

* fix: removed the LOCs that were failing the build

* fix:removed the unrequired LOCs that were failing the build

* fix: UI breaking at some dimensions, made it responsive

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>

* Revamp blog article page: modular components, CSS Grid layout, new author cards, sidebar, TOC tooltip, responsive typography, and Keploy-themed design

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>

* Remove category pill (Technology tag) from blog sidebar

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>

* fix:made tags clickable that navigates to tag's blog listing page

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>

---------

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
* feat: setup Playwright e2e testing infrastructure

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* feat: add environment variables for WordPress API in Playwright config

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* feat: enhance Playwright tests with new homepage spec and build steps

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* fix: update Playwright config to use hardcoded WordPress API URLs

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* fix: update Playwright workflow permissions and add continue-on-error for PR comments

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* test infrastructure and workflow updates.

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* test: Add comprehensive Playwright E2E for navigation and more stories  component.

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* Mock node js server

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* Mock json data

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* test: extensive Playwright test coverage for components

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* Fixing CI Failures for webkit browser

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* Component Test - Footer and Heropost

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* Fixing flaky and failed test for webkit browser

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* Playwright test for PostHeader as well as PostBody

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* Removing the PR commenting feature for now

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* WebKit error fixed

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* MoreStories webkit error fixed

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* Fixing flaky MoreStories webkit test

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* Update tests/components/PostHeader.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Update tests/components/PostBody.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Refactor viewport checks in Navigation and HomePage tests for improved clarity

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>

* Add tests for ScrollToTop component functionality and accessibility

Signed-off-by: Deep <deep@example.com>

* Add tests for TableContents component functionality on desktop and mobile

Signed-off-by: Deep <deep@example.com>

* Add tests for Tag component functionality and accessibility

Signed-off-by: Deep <deep@example.com>

* Add tests for Testimonials component functionality and accessibility

Signed-off-by: Deep <deep@example.com>

* Add tests for TopBlogs component functionality and accessibility

Signed-off-by: Deep <deep@example.com>

* E2E Test for technology page

Signed-off-by: Deep <deep@example.com>

* E2E Test for technology post page

Signed-off-by: Deep <deep@example.com>

* Update tests/components/Tag.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Update tests/components/ScrollToTop.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Update tests/components/Tag.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Update tests/pages/TechnologyPostPage.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Update tests/components/TableContents.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Update tests/pages/TechnologyPage.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Refactor navigation logic in PostHeader and ScrollToTop tests to use href attribute for improved reliability

Signed-off-by: Deep <deep@example.com>

* Playwright config and CI changes

Signed-off-by: Deep <deep@example.com>

* Using build server instead of dev

Signed-off-by: Deep <deep@example.com>

* remove waitForTimeout as Playwright natively handles dynamic UI readiness

Signed-off-by: Deep <deep@example.com>

* remove waitForTimeout as Playwright natively handles dynamic UI readiness-2

Signed-off-by: Deep <deep@example.com>

* Removing gaurds in core structural elements

Signed-off-by: Deep <deep@example.com>

* refactor: simplify visibility checks for LinkedIn and Slack social links in footer tests

Signed-off-by: Deep <deep@example.com>

* refactor: add data-testid attributes for improved test targeting in components

Signed-off-by: Deep <deep@example.com>

* test: add data-testid attributes for footer and tags sections to improve test targeting

Signed-off-by: Deep <deep@example.com>

* Update tests/components/ScrollToTop.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Update tests/components/TopBlogs.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Update tests/components/TableContents.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Update tests/components/MoreStories.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Update tests/components/PostBody.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Update tests/components/ScrollToTop.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Update tests/components/TableContents.spec.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Add tests for Author Detail Page and Authors Index Page component availability

Signed-off-by: Deep <deep@example.com>

* Add tests for Community Page, Community Post Page, and Community Search Page component availability

Signed-off-by: Deep <deep@example.com>

* Add tests for Tag Detail Page and Tags Index Page component availability

Signed-off-by: Deep <deep@example.com>

* Add tests for Not Found Page and Search Page component availability

Signed-off-by: Deep <deep@example.com>

* Add tests for Mobile Layout and Navigation responsiveness

Signed-off-by: Deep <deep@example.com>

* Add tests for API Mocking and SEO Meta Tags configuration

Signed-off-by: Deep <deep@example.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Refactor tests for improved visibility checks and assertions in Navigation, PostHeader, ScrollToTop, TableContents, HomePage, and TechnologyPostPage components

Signed-off-by: Deep <deep@example.com>

* Update component visibility assertions in various test files for improved accuracy

Signed-off-by: Deep <deep@example.com>

* Comment out unused device configurations for Firefox and Webkit in Playwright config

Signed-off-by: Deep <deep@example.com>

* Add community posts fixture and update mock server to handle community category queries

Signed-off-by: Deep <deep@example.com>

* Simplify Playwright E2E tests configuration by removing browser matrix and hardcoding to chromium

Signed-off-by: Deep <deep@example.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Enhance component tests for improved visibility and accuracy across various pages

Signed-off-by: Deep <deep@example.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Enhance test reliability by adding timeout and visibility checks in various components

Signed-off-by: Deep <deep@example.com>

* Update @types/node to version 18.19.130 and add engines field for Node.js compatibility

Signed-off-by: Deep <deep@example.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Enhance mobile layout tests by adding max width checks and improving visibility assertions

Signed-off-by: Deep <deep@example.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Refactor TOC mobile tests to improve dropdown item selection and visibility checks

Signed-off-by: Deep <deep@example.com>

* Refactor TOC component tests to remove conditional click and ensure visibility checks for first post

Signed-off-by: Deep <deep@example.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Refactor Playwright configuration and mobile layout tests to improve horizontal overflow checks

Signed-off-by: Deep <deep@example.com>

* Refactor horizontal overflow epsilon to use WIDTH_EPSILON for consistency in mobile layout tests

Signed-off-by: Deep <deep@example.com>

* Refactor test interactions to ensure visibility and enablement before clicks across multiple components

Signed-off-by: Deep <deep@example.com>

* Refactor tests in ScrollToTop, TableContents, TechnologyPostPage, and MobileNavigation for improved visibility and reliability of elements before interactions

Signed-off-by: Deep <deep@example.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>

* Refactor navigation tests to improve URL wait conditions and enhance locator strategies for better reliability

Signed-off-by: Deep <deep@example.com>

* Refactor environment variable loading to improve error handling and provide clearer warnings when .env.test is missing

Signed-off-by: Deep <deep@example.com>

* fix: improve test stability by adding timeouts to element visibility checks and refining URL wait conditions in tests.

Signed-off-by: Deep <deep@example.com>

* refactor: Enhance Playwright tests by adding data-testid attributes to components and improving test selectors.

Signed-off-by: Deep <deep@example.com>

* refactor: enhance Playwright test reliability by using explicit waits for content and data-testid locators.

Signed-off-by: Deep <deep@example.com>

---------

Signed-off-by: DSingh0304 <deepshekhar0306@gmail.com>
Signed-off-by: Deep Shekhar Singh <deepshekhar0306@gmail.com>
Signed-off-by: Deep <deep@example.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Deep <deep@example.com>
Co-authored-by: Manas Manohar <21006907+manasmanohar@users.noreply.github.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
* Add sidebar ad image with fallback, clickable signup link, and orange glow hover effect

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>

* resolved copilot comments

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>

---------

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Co-authored-by: Neha Gupta <gneha21@yahoo.in>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
Add analytics script and whitelist its endpoint in CSP connect-src.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Neha Gupta <gneha21@yahoo.in>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
* fix: update analytics and CSP configuration

Add analytics script and whitelist its endpoint in CSP connect-src.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve CSP duplication and Cache-Control scoping issues in vercel.json

* fix: move telemetry script to _app.tsx and remove unsafe Cache-Control from vercel.json

---------

Signed-off-by: Neha Gupta <gneha21@yahoo.in>
Co-authored-by: Shubham Jain <shubhamkjain@outlook.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Neha Gupta <gneha21@yahoo.in>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
* aeo update

Signed-off-by: Neha Gupta <gneha21[at]yahoo>

* fix: add llms-full.txt reference to blog llms.txt

Blog llms.txt was missing a pointer to the comprehensive llms-full.txt,
reducing discoverability for AI consumers that only read llms.txt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* feat: add canonical URLs and og:url to blog posts

- Add canonicalUrl prop to Meta and Layout components
- Render <link rel="canonical"> and <meta property="og:url"> per post
- Add og:type=article meta tag
- Pass canonicalUrl from community/[slug] and technology/[slug] pages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: resolve all 6 copilot review comments on PR #356

aiReferralTracker.ts:
- Parse referrer with URL() and match hostname exactly or via
  endsWith('.'+domain) to prevent substring false positives
- Declare Window.dataLayer type globally, use ??= for type safety
- Remove all (window as any) casts

_app.tsx:
- Track AI referrals on route changes via Router.events, not just
  initial mount

meta.tsx:
- Make og:type configurable via prop (defaults to "article")
- Homepage now passes ogType="website"

_document.tsx:
- Use getOrganizationSchema() from lib/structured-data instead of
  inline duplicate Organization schema

index.tsx:
- Remove duplicate Organization schema (already in _document.tsx)
- Pass ogType="website" to Layout

layout.tsx:
- Pass ogType prop through to Meta component

SeoMeta.spec.ts:
- Add tests for canonical URL, og:url on post pages
- Add test for og:type="website" on homepage
- Add test for og:type="article" on post pages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: resolve 4 copilot review comments (round 2)

pages/index.tsx:
- Remove unused getOrganizationSchema import

pages/_app.tsx:
- Revert route-change tracking — document.referrer doesn't change on
  SPA navigations, so re-firing would duplicate ai_referral events.
  Track only on initial landing.

utils/aiReferralTracker.ts:
- Widen dataLayer type to unknown[] to avoid TypeScript friction with
  gtag's non-record pushes

pages/_document.tsx:
- Use shared constants (SITE_URL, ORG_NAME, MAIN_SITE_URL) from
  lib/structured-data instead of hard-coded URLs in Blog schema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: resolve 4 copilot review comments (round 3)

layout.tsx:
- Default ogType to "website" instead of "article" — non-post pages
  (tag, author, search) were incorrectly emitting og:type=article

technology/[slug].tsx, community/[slug].tsx:
- Pass ogType="article" explicitly from post detail pages
- Only set canonicalUrl when post slug is available (not during
  fallback render) to avoid incorrect canonical on loading state

SeoMeta.spec.ts:
- Add e2e test for AI referral tracker — verifies dataLayer event
  is pushed on UTM-attributed landing (?utm_source=chatgpt)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: resolve 2 copilot review comments (round 4)

SeoMeta.spec.ts:
- Use page.waitForFunction instead of immediate evaluate to avoid
  flaky test — trackAiReferral runs in useEffect after hydration

llms.txt:
- Clarify that llms-full.txt is hosted on the main site (landing),
  not in blog-website repo. URL is correct since blog is proxied
  through landing's Vercel deployment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

---------

Signed-off-by: Neha Gupta <gneha21[at]yahoo>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>
Co-authored-by: Neha Gupta <gneha21[at]yahoo>
Co-authored-by: slayerjain <shubhamkjain@outlook.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
* feat: enrich blog structured data and llms.txt for GEO/AEO

lib/structured-data.ts:
- Add dateModified, articleSection to BlogPosting schema
- Add author Person with url using sanitizeAuthorSlug (matches actual routes)
- Add publisher logo width/height for ImageObject compliance
- Add getBlogSchema() export using shared constants

lib/api.ts:
- Add modified field to PostFields GraphQL fragment

pages/_document.tsx:
- Add Organization and Blog JSON-LD via shared helpers (single source)

pages/technology/[slug].tsx, pages/community/[slug].tsx:
- Pass dateModified and articleSection to BlogPosting schema

public/llms.txt:
- Rewrite with blog architecture, 500+ articles, categories, awards,
  products, author pages, and comparison hub link

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: resolve 2 copilot comments on PR #359

lib/structured-data.ts:
- Handle ppmaAuthorName as array (sometimes returned as array
  from WordPress) — take first element if array

public/llms.txt:
- Clarify author URL uses sanitized slug format, not raw name

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: align authorName type with runtime handling

Type authorName as string | string[] to match WordPress
ppmaAuthorName which can be either format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: resolve 2 copilot comments on PR #359

lib/structured-data.ts:
- Handle empty authorName array by falling back to ORG_NAME
- Remove hardcoded logo width/height that don't match actual asset

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* Update public/llms.txt

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: resolve 4 copilot comments on PR #359

lib/structured-data.ts:
- Only set author.url for real authors, not org fallback
  (avoids /authors/keploy 404)

tests/fixtures:
- Add modified field to single-post, technology-posts,
  community-posts mock fixtures

public/llms.txt:
- Remove hardcoded star count that goes stale

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: add modified field to revision node query

Ensures dateModified in JSON-LD stays accurate during preview/revision
flows where Object.assign overwrites post fields from revision data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: resolve 3 copilot comments on PR #359

lib/structured-data.ts:
- Use BLOG_NAME constant instead of hardcoded string in getBlogSchema
- Simplify Blog publisher logo to URL string (SVG has no fixed dimensions)

types/post.ts:
- Add optional modified field to Post interface

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: restore ImageObject for Blog publisher logo

Keep consistent with BlogPosting publisher logo format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: use primitive number type instead of boxed Number

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: add actual logo dimensions to BlogPosting publisher

Group.png is 462x539 — added matching width/height to ImageObject.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: resolve 4 copilot comments on PR #359

- Reformat all test fixture JSON files with consistent indentation
- Use ORG_LOGO_URL in getBlogSchema publisher logo for consistency
  with getOrganizationSchema and getBlogPostingSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

---------

Signed-off-by: slayerjain <shubhamkjain@outlook.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Neha Gupta <gneha21@yahoo.in>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
* fix: repair malformed vercel.json (missing closing braces)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: resolve 799 duplicate meta descriptions flagged by Bing

Root cause: listing pages (tag, author, search, index) used hardcoded
or ultra-generic meta descriptions, not WordPress content.

- community/index: fix copy-paste bug ("Technology" → "Community")
- technology/index: unique 155-char description
- tag/[slug]: dynamic description including tag name
- tag/index: unique description for tag listing
- authors/[slug]: dynamic description including author name
- authors/index: unique description for author listing
- search, community/search: conditional description with empty-state
- community/[slug], technology/[slug]: quality gate for Yoast metaDesc
  (fallback to title-based description if Yoast desc is <60 chars)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* perf: fix blog Core Web Vitals (LCP, CLS, TBT)

Font loading:
- Remove render-blocking @import for Baloo 2 from CSS
- Add non-blocking font preloads with print→all swap pattern
- Remove duplicate per-page DM Sans link tags

Third-party scripts:
- Move all 5 analytics scripts to lazyOnload strategy
- Remove duplicate telemetry script from _document.tsx

Image optimization:
- Add priority prop to CoverImage for LCP (post header only)
- Add sizes attribute and explicit loading prop
- Convert raw <img> to next/image in BlogSidebar
- Add AVIF/WebP formats and 1-year cache TTL in next.config
- Add content-visibility:auto and aspect-ratio for WP images

JavaScript bundle:
- Dynamic import CodeMirror, BlogSidebar, JsonDiffViewer
- Remove duplicate PrismJS from slug pages
- Replace framer-motion with CSS @Keyframes fadeIn (~30-40kB saved)
- Remove AnimatePresence wrapper from _app.tsx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: technical SEO — clean up sitemap, add canonicals, fix duplicates

- Remove 16 problematic URLs from static sitemap (non-blog URLs,
  wrong-category duplicates)
- Cross-category duplicate fix: posts at wrong /community/ or
  /technology/ path now 301 redirect to correct category
- Add canonical URLs to 7 listing pages (index, tag, author)
- Add noindex to search pages
- Fix soft 404 on empty author pages (return proper 404)
- Fix 302→301 redirects on technology slug pages
- Fix robots.txt sitemap URL (www.keploy.io → keploy.io)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: resolve 12 copilot review comments on PR #362

tag/[slug].tsx:
- Handle undefined/string[] router.query.slug for title/description

technology/[slug].tsx + community/[slug].tsx:
- Add safeTitle guard for fallback render (prevents "undefined")
- Apply description quality gate to both Layout and JSON-LD schema
- Validate category redirect against ['community','technology'] allowlist
- Fix unsafe post?.seo.title access

_document.tsx:
- Replace broken string onLoad with preload+stylesheet pattern
  (React ignores string event handlers in JSX)

post-body.tsx:
- Fix misleading comment about CodeMirror lazy imports

post-body.module.css:
- Replace unsupported attr() aspect-ratio with fixed 16/9

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: address remaining Copilot review comments on PR #362

- Category redirect: validate against ['community','technology'] allowlist,
  return notFound for unknown categories instead of guessing
- Community redirect: use data.post.slug (canonical CMS slug) not request param
- Fallback description: return generic description during router.isFallback
  instead of "Learn about Loading..."
- CSS: remove forced 16/9 aspect-ratio on WP images (browsers use intrinsic ratio)
- BlogSidebar: add sizes="320px" to next/image to prevent oversized downloads
- CodeMirror: clarify comment about lazy loading (PostBody is the dynamic boundary)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: address round 2 Copilot review on blog PR #362

- Update E2E test assertion for new technology index description
- Strip HTML tags and entities from post.title before meta/JSON-LD
- Return notFound for invalid/empty author slugs (was soft 404)
- Add contain-intrinsic-size to content-visibility:auto for CLS stability
- Correct framer-motion removal comment in layout.tsx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: pass tag slug as prop from getStaticProps, make CoverImage sizes configurable

- tag/[slug]: pass tagSlug from getStaticProps so SSR meta tags are correct
- CoverImage: make sizes a prop (default 780px for post header, overridable
  for smaller card contexts)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: replace cross-category redirect with notFound to fix prerendering error

redirect cannot be returned from getStaticProps during prerendering.
Return notFound instead — the post will be served from its correct
category path at runtime via ISR fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: E2E failure — add community post fixture to mock server, decode HTML entities

- Add single-community-post.json fixture with category "community"
- Mock server now returns community-categorized post for community slugs
- Decode HTML entities in safeTitle instead of stripping them (&#8217; → ')

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: restore 301 redirect for cross-category posts to preserve SEO signals

notFound drops link equity. Redirect is safe because getStaticPaths only
returns same-category paths — the redirect only fires at ISR runtime for
fallback pages, not during next build prerendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: extract SEO helpers, fix fixture quotes, add motion-reduce

- Extract sanitizeTitle/getSafeDescription to utils/seo.ts (shared between
  technology and community slug pages)
- Fix single-community-post.json: use double-quoted HTML attrs to match
  the regex patterns in page code
- Add motion-reduce:animate-none to layout fade-in for accessibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: harden SEO helpers — sanitize metaDesc, handle empty title, normalize whitespace

- sanitizeTitle returns empty string instead of 'Loading...' placeholder
- getSafeDescription sanitizes metaDesc (strip tags, decode entities, trim)
  before length gate
- Handle empty safeTitle gracefully in fallback description
- Normalize whitespace in all decoded strings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

---------

Signed-off-by: slayerjain <shubhamkjain@outlook.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Neha Gupta <gneha21@yahoo.in>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
* fix: crash on 404 page when post title or excerpt is null

WordPress can return posts with null title/excerpt. NotFoundPage.tsx
called .toLowerCase() on these without null-checking, causing
"Cannot read properties of null" during both SSR prerendering and
client-side rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: address Copilot review — null-safe filtering across all search components

- Precompute normalizedSearchTerm to avoid repeated toLowerCase() calls
- Apply null-safe filtering to more-stories.tsx and community/search.tsx
- Update Post type: title and excerpt are string | null (matches WP reality)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: normalize null title/excerpt at component boundary + harden getExcerpt

- NotFoundPage: normalize allPosts once via .map() so downstream
  rendering (getExcerpt, dangerouslySetInnerHTML) never sees null
- getExcerpt: return empty string for null/undefined input
- Mock fixture: add post with null title/excerpt to catch regressions
  in E2E tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: normalize null title/excerpt in more-stories and community search

Posts with null title/excerpt from WordPress are now defaulted to empty
strings after filtering, before they reach rendering components
(HeroPost, getExcerpt, dangerouslySetInnerHTML).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: add postId to null fixture, clean up redundant fallbacks, add E2E test

- Add missing postId to null title/excerpt test fixture
- Remove redundant || '' in filter (allPosts.map already normalizes)
- Add E2E test asserting no pageerror events on 404 page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: normalize null posts at API layer, revert Post type to non-nullable

- Add normalizePostNode/normalizePostEdges in lib/api.ts — defaults
  null title/excerpt to empty string at the data boundary
- Revert Post.title/excerpt back to string (not nullable) since API
  layer now guarantees non-null values
- Fix E2E test: register pageerror handler before navigation, use
  dedicated 404 URL instead of page.url()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: normalize revision node before Object.assign to prevent null re-introduction

getPostAndMorePosts() applies revision data via Object.assign which can
overwrite the already-normalized title/excerpt with null values from the
revision node. Normalize the revision before applying it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

---------

Signed-off-by: slayerjain <shubhamkjain@outlook.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Neha Gupta <gneha21@yahoo.in>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
slayerjain and others added 10 commits May 27, 2026 17:29
…l.txt (#364)

* fix: crash on 404 page when post title or excerpt is null

WordPress can return posts with null title/excerpt. NotFoundPage.tsx
called .toLowerCase() on these without null-checking, causing
"Cannot read properties of null" during both SSR prerendering and
client-side rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: slayerjain <shubhamkjain@outlook.com>

* fix: address Copilot review — null-safe filtering across all search components

- Precompute normalizedSearchTerm to avoid repeated toLowerCase() calls
- Apply null-safe filtering to more-stories.tsx and community/search.tsx
- Update Post type: title and excerpt are string | null (matches WP reality)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: normalize null title/excerpt at component boundary + harden getExcerpt

- NotFoundPage: normalize allPosts once via .map() so downstream
  rendering (getExcerpt, dangerouslySetInnerHTML) never sees null
- getExcerpt: return empty string for null/undefined input
- Mock fixture: add post with null title/excerpt to catch regressions
  in E2E tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: normalize null title/excerpt in more-stories and community search

Posts with null title/excerpt from WordPress are now defaulted to empty
strings after filtering, before they reach rendering components
(HeroPost, getExcerpt, dangerouslySetInnerHTML).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: add postId to null fixture, clean up redundant fallbacks, add E2E test

- Add missing postId to null title/excerpt test fixture
- Remove redundant || '' in filter (allPosts.map already normalizes)
- Add E2E test asserting no pageerror events on 404 page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: normalize null posts at API layer, revert Post type to non-nullable

- Add normalizePostNode/normalizePostEdges in lib/api.ts — defaults
  null title/excerpt to empty string at the data boundary
- Revert Post.title/excerpt back to string (not nullable) since API
  layer now guarantees non-null values
- Fix E2E test: register pageerror handler before navigation, use
  dedicated 404 URL instead of page.url()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: normalize revision node before Object.assign to prevent null re-introduction

getPostAndMorePosts() applies revision data via Object.assign which can
overwrite the already-normalized title/excerpt with null values from the
revision node. Normalize the revision before applying it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* feat(seo): GEO/AEO Phase 1 — fix meta tags, favicon paths, robots.txt, and add llms-full.txt

- Fix favicon paths in meta.tsx: add /blog/ prefix and .png extension
- Add og:site_name and og:locale meta tags for better social sharing
- Fix subscribe-newsletter alt text from generic "Image" to descriptive
- Remove invalid subdomain path disallows from robots.txt
- Add llms-full.txt for comprehensive AI/LLM crawler content
- Update llms.txt to link to llms-full.txt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: address Copilot review — remove redundant .map() normalization, harden test

- Remove redundant .map() normalization in search, more-stories, NotFoundPage
  since fetchAPI already normalizes title/excerpt to empty strings
- Use null-safe access in filter predicates instead
- Harden NotFoundPage test: wait for load state, capture console errors,
  add post-render buffer to catch hydration errors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* perf: memoize allPosts and filteredAllPosts in NotFoundPage

Wrap with useMemo to avoid O(n) recomputation on every countdown
tick (component re-renders every second).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix(test): only catch pageerror in 404 test, not console.error

Console.error captures CSP violations and network failures from the
test environment that are unrelated to our code changes. Revert to
pageerror-only which catches actual uncaught exceptions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

---------

Signed-off-by: slayerjain <shubhamkjain@outlook.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Neha Gupta <gneha21@yahoo.in>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
* fix: GSC audit — 28 duplicate URL redirects, OG tags, favicon, dateModified

- Added 28 permanent redirects for duplicate /community/ and /technology/
  blog URLs based on WordPress category mapping
- Added 2 broken backlink redirects (end-to-end-testing, cursor-vs-copilot)
- Added article:published_time meta tag to post pages
- Fixed favicon paths to reference correct files with /blog/ prefix
- Fixed structured data logo URL
- Removed duplicate font preload from authors page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: update publisher.logo dimensions to 512x512 to match android-chrome-512x512.png

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

---------

Signed-off-by: Neha Gupta <gneha21@yahoo.in>
Co-authored-by: Neha Gupta <gneha21@yahoo.in>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
Signed-off-by: Alok Kumar <101720005+ALOK07K@users.noreply.github.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
* fix: GSC audit — 28 duplicate URL redirects, OG tags, favicon, dateModified

- Added 28 permanent redirects for duplicate /community/ and /technology/
  blog URLs based on WordPress category mapping
- Added 2 broken backlink redirects (end-to-end-testing, cursor-vs-copilot)
- Added article:published_time meta tag to post pages
- Fixed favicon paths to reference correct files with /blog/ prefix
- Fixed structured data logo URL
- Removed duplicate font preload from authors page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix: update publisher.logo dimensions to 512x512 to match android-chrome-512x512.png

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* ci: add IndexNow auto-submission for latest 100 blog posts

Queries WordPress GraphQL for 100 most recently modified posts and
submits their URLs to Bing IndexNow on every push to main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* ci: add daily cron and manual trigger for IndexNow blog submission

Queries WordPress for 100 most recently modified posts and submits
to Bing IndexNow. Runs on push to main, daily at 6 AM UTC, or manually.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* ci: run IndexNow every 12 hours instead of daily

Blog edits happen daily, so twice-daily (6 AM + 6 PM UTC) ensures
modified posts get submitted to Bing promptly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* ci+seo: harden IndexNow workflow, add dynamic blog sitemap, test article:published_time

IndexNow workflow
- Use working key feeeddbe661a4686ba6e4d5cb663b2cb (keys are per-host;
  this is the same key served from keploy.io/<key>.txt by landing, so
  it's already verified by Bing)
- Add keyLocation to payload
- set -euo pipefail and curl --fail-with-body so fetch/parse errors
  aren't masked
- Capture CURL_EXIT separately from HTTP_CODE; exit non-zero with
  ::error:: on both curl failure and real non-2xx (2xx and 202 accepted
  per IndexNow spec)
- Top 20 most recently modified posts instead of 100
- 24h cron instead of 12h
- Troubleshooting block with keyLocation verify + POST reproducer
- tr -d whitespace on URL_COUNT so log reads "Submitted 20 URLs" not
  "Submitted   20 URLs"

Dynamic sitemap (pages/sitemap.xml.tsx)
- Rebuilds sitemap-blog.xml from scratch directly from WP GraphQL
- Paginated fetch of all posts, not just first 50
- Includes /blog, /blog/community, /blog/technology index pages
- Cache-Control: s-maxage=86400, stale-while-revalidate=86400 so at most
  ~1 WP fetch per day per Vercel edge region (no build-time cost, no
  cron-triggered deploys)
- Replaces the hand-maintained public/sitemap.xml frozen at 2024-03-07

Playwright SEO meta coverage
- Assert meta[property="article:published_time"] exists on post pages
  and matches an ISO-8601 datetime so regressions in the new
  publishedDate pipeline are caught in CI

Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix(ci+sitemap): surface WP GraphQL errors + add sitemap E2E coverage

- pages/sitemap.xml.tsx: throw when WPGraphQL returns a non-empty top-level
  errors array so partial/empty data can't silently render an incomplete
  sitemap (matches the error-handling contract in lib/api.ts fetchAPI)
- .github/workflows/indexnow.yml: jq-check for an errors array in the WP
  response and fail the run with ::error:: before submitting, so scheduled
  runs reliably surface CMS/query issues instead of quietly falling back
  to submitting just /blog/
- tests/e2e/SeoMeta.spec.ts: assert /sitemap.xml returns HTTP 200, an
  application/xml content type, and at least the three static entries
  (/blog, /blog/community, /blog/technology) that are always rendered
  regardless of WP fetch results

Signed-off-by: nehagup <15074229+nehagup@users.noreply.github.com>

* test(e2e): use absolute URL in sitemap test to respect /blog basePath

Playwright's request.get resolves a leading-slash path against the host
rather than against baseURL's path, so request.get('/sitemap.xml') was
hitting http://localhost:3000/sitemap.xml and bypassing Next.js's /blog
basePath — the server returned 404 instead of the dynamic sitemap.

Pass the full URL via the baseURL fixture (\${baseURL}/sitemap.xml) so
it resolves to http://localhost:3000/blog/sitemap.xml, matching how
page.goto is used in the other tests in this file.

Signed-off-by: nehagup <15074229+nehagup@users.noreply.github.com>

* fix(sitemap+ci): reuse MAIN_SITE_URL, degrade gracefully on WP failure

- pages/sitemap.xml.tsx: import MAIN_SITE_URL from lib/structured-data
  instead of duplicating the canonical host, so a future domain change
  only touches one file
- pages/sitemap.xml.tsx: catch WP fetch/GraphQL errors in
  getServerSideProps and render a valid sitemap containing at least the
  three static blog entries (/blog, /blog/community, /blog/technology)
  rather than returning 500. Degraded responses get a much shorter 5min
  edge cache so a transient WP outage doesn't pin the stripped-down
  sitemap for a full day, and the error is still logged to Vercel
  function logs for alerting
- .github/workflows/indexnow.yml: extend the WP-GraphQL-errors branch
  with a Troubleshooting block (endpoint health curl, GraphiQL link,
  common causes) matching the IndexNow-failure block, so operators
  can act on the CI error without context switching
- tests/e2e/SeoMeta.spec.ts: use baseURL! non-null assertion in the
  sitemap test, matching the convention in the rest of the file

Signed-off-by: nehagup <15074229+nehagup@users.noreply.github.com>

* fix(favicon): replace Next.js default favicons with Keploy mascot

Every file in public/favicon/ was the Next.js starter template:
- favicon.ico / *.png were the "N" logo
- safari-pinned-tab.svg was the "N" silhouette
- site.webmanifest had name "Next.js", theme #000000, and referenced
  /favicons/* (wrong directory and missing /blog basePath)
- browserconfig.xml had tile color #000000 and the same wrong path

Regenerated from docs/static/img/favicon.png (1280x1496 Keploy mascot)
padded to a square RGBA canvas and resized to 16, 32, 150, 180, 192,
512. favicon.ico contains 16/32/48 multi-resolution. safari-pinned-tab
now uses the monochrome Keploy logo from docs/static/keploy-logo.svg.

site.webmanifest: name=Keploy Blog, short_name=Keploy, icons point at
/blog/favicon/*, theme_color=#FF914D (Keploy orange), background
bumped from #000000 to #ffffff to match the light blog theme.

browserconfig.xml: tile path /blog/favicon/mstile-150x150.png,
TileColor=#FF914D.

Signed-off-by: nehagup <15074229+nehagup@users.noreply.github.com>

* fix(sitemap): make degraded-path log actionable for on-call

Replace the single-line console.error with a structured payload
containing (a) the effective WP GraphQL endpoint, (b) whether
WORDPRESS_API_URL env var is set (so wrong-env issues vs real WP
outages are distinguishable from a single log line), (c) the error
message, and (d) a numbered nextSteps array — health-check curl,
plugin/host check, schema validation in GraphiQL, env var fix, and
self-heal window. Goal: on-call can diagnose from Vercel logs alone
without needing to cross-reference the sitemap code.

Signed-off-by: nehagup <15074229+nehagup@users.noreply.github.com>

* fix(sitemap): drop misleading WORDPRESS_API_URL fallback

next.config.js hard-requires WORDPRESS_API_URL at startup (throws if
the env var is unset or can't be URL-parsed), so the
`|| "https://wp.keploy.io/graphql"` fallback and the `envVarSet`
degraded-log field were effectively dead code in any running build —
which is misleading when reading the file in isolation.

Read the env var directly (cast to string since next.config.js has
already validated it), drop the envVarSet field from the structured
log, and rephrase the fourth next-step from "if env var is unset"
to "if the endpoint logged above is unexpected, double-check
WORDPRESS_API_URL in Vercel settings" — same diagnostic value,
without pretending there's a runtime fallback.

Signed-off-by: nehagup <15074229+nehagup@users.noreply.github.com>

* fix(favicon): sync theme-color + msapplication-TileColor with manifest

site.webmanifest was updated to theme_color #FF914D (Keploy orange) in
the earlier favicon commit, but the matching meta[name="theme-color"]
and meta[name="msapplication-TileColor"] tags in components/meta.tsx
were still pointing at #000000/#000 — the Next.js starter values.

That caused platform-inconsistent theming: PWA install prompt shows
Keploy orange (reads manifest), Chrome address bar + Windows Start
tile show black (read meta tags). Bringing both to #FF914D for a
single consistent brand color across every surface.

Signed-off-by: nehagup <15074229+nehagup@users.noreply.github.com>

---------

Signed-off-by: Neha Gupta <gneha21@yahoo.in>
Signed-off-by: nehagup <15074229+nehagup@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: nehagup <15074229+nehagup@users.noreply.github.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
…373)

* fix(robots): nuanced AI bot policy — allow search, block training

Previously the blog allowed GPTBot, ClaudeBot, anthropic-ai, CCBot,
Google-Extended, Applebot-Extended, Meta-ExternalAgent — i.e., training
crawlers — alongside the search variants. This gave content to future
model training runs without any visibility benefit in current answer
engines.

New policy:
- Allow AI SEARCH bots (drive answer visibility): OAI-SearchBot,
  ChatGPT-User, Claude-SearchBot, Claude-User, PerplexityBot,
  Perplexity-User, Gemini-Deep-Research, GoogleOther, Applebot,
  DuckAssistBot, Amazonbot.
- Block TRAINING-ONLY bots: GPTBot, ClaudeBot, anthropic-ai, CCBot,
  Google-Extended, Applebot-Extended, Meta-ExternalAgent, FacebookBot,
  cohere-ai, Diffbot, Omgilibot, ImagesiftBot.
- Keep Bytespider blocked.

Matches Speedscale / Katalon / Testsigma best-practice from the 2026
competitor audit. Reopens Task 52 per user direction 2026-04-14
(supersedes Q9 allow-all stance). Mirrors the corresponding policy
change on the landing site.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix(authors): LIVE-11 — add <title> + Person schema + better Title

Author pages were returning 200 OK with <title></title> empty because:
1. components/meta.tsx emits twitter:title and og:title but not an
   actual <title> tag.
2. pages/authors/[slug].tsx passed Title to Layout but never added
   its own <title> to the Head, unlike pages/community/[slug].tsx.

The result was that keploy.io/blog/authors/keploy-team and every
other author page had completely empty document titles, which killed
CTR in search results and gave AI crawlers no signal about the
author entity.

Changes:
- Add next/head import and emit <title>{pageTitle}</title> explicitly.
- Default authorName to "Keploy Author" when missing instead of
  letting undefined propagate into the template (was producing
  "undefined Page").
- Rename page title format from "{name} Page" to
  "{name} — Keploy Blog Author" for clearer search snippets.
- Extract author URL / slug once, reuse everywhere.
- Add Person JSON-LD schema to structuredData array. Minimum viable
  set (name, url, jobTitle, worksFor, knowsAbout) — full sameAs
  (LinkedIn / GitHub / Twitter) is deferred to GEO-05 which requires
  per-author consent on which links to publish.

Verify after deploy:
  curl -s https://keploy.io/blog/authors/keploy-team | \
    python3 -c "import re,sys; h=sys.stdin.read(); \
    print('title:', re.findall(r'<title[^>]*>([^<]*)</title>', h)[0]); \
    print('Person count:', h.count('\"@type\":\"Person\"'))"
  # expected: non-empty title, 1+ Person blocks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix(redirects): Task 42 + Task 43 + Task 46 — 28 cross-category + broken backlink redirects

Task 42 — Resolve 26 blog posts accessible at BOTH /blog/community/
and /blog/technology/ URLs. Google flags these as duplicate content.
Per WP category mapping, add 301 redirects so each post resolves to
its canonical category.

  13 posts: technology is canonical, community redirects to it
    (bitbucket-self-hosting-running-ebpf..., building-a-cli-tool-in-go,
    create-stunning-parallax-animations, gemini-pro-vs-openai-benchmark,
    how-to-use-covdata, integration-of-e2e-testing-in-a-cicd-pipeline,
    mastering-nyc, migration-guide-from-restassured-to-keploy,
    protocol-parsing-guide, revolutionising-unit-test-generation-with-llms,
    scram-authentication, secure-your-database-communications,
    using-tc-bpf-program)

  13 posts: community is canonical, technology redirects to it
    (canary-testing, codium-vs-copilot, decoding-brd, decoding-http2,
    how-to-generate-test-cases, mock-vs-stub-vs-fake, performance-testing,
    python-get-current-directory, top-5-cypress-alternatives,
    understanding-branch-coverage, understanding-statement-coverage,
    what-is-postgres-wire-protocol, writing-test-cases-for-cron-jobs)

Task 43 — Fix broken backlink /blog/community/end-to-end-testing-and-why-do-you-need-it
(8 external backlinks from DR 8-74 domains: dlatesterow.pl,
electronicsweekly.com, erpwebtutor.com, apsense.com, sqlgulf.org,
nftweightlossforum.com, sobergroup.com). Redirect to the closest
existing post: integration-of-e2e-testing-in-a-cicd-pipeline.

Task 46 — Fix broken malformed-double-path URL
/blog/community/https-keploy-io-blog-community-cursor-vs-github-copilot
(1 external backlink). Redirect to the canonical cursor-vs-copilot slug.

Total redirects go from 4 → 32. All validated via JSON parse.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* feat(schema): GEO-13 — TechArticle schema for blog/technology posts

blog/community posts stay as BlogPosting. blog/technology posts now
emit @type=TechArticle, which is the schema.org type specifically
intended for 'a technical article — typically an on-line manual,
describing how to accomplish a task.' AI models (Perplexity, ChatGPT,
Claude) weight TechArticle higher than generic BlogPosting for
developer-oriented queries.

Changes:
- lib/structured-data.ts: add categorySlug, dependencies, and
  proficiencyLevel to BlogPostingInput. Branch @type on categorySlug.
  TechArticle-specific fields (dependencies, proficiencyLevel) are
  only attached when @type === TechArticle.
- pages/technology/[slug].tsx: pass categorySlug: 'technology' and
  proficiencyLevel: 'Intermediate' (conservative default).

pages/community/[slug].tsx intentionally unchanged — community posts
remain BlogPosting per GEO-13 design.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix(copilot-review): 4 comments on blog PR #373

Comment 1 - public/robots.txt /api/proxy-image:
  Disallow: /api/ was blocking same-origin image proxy endpoint used
  by UI components to render external avatars. Added explicit
  Allow: /api/proxy-image above the broader /api/ disallow.

Comment 2 - public/robots.txt /search contradiction:
  pages/search.tsx already emits <meta name="robots" content="noindex,
  follow" />. Blocking via robots.txt would hide the noindex signal
  and cause 'Indexed, though blocked by robots.txt' warnings. Removed
  the Disallow: /search line and kept the meta tag as the single
  source of truth. Added inline comment explaining the choice.

Comment 3 - pages/authors/[slug].tsx worksFor.url:
  Person.worksFor.url was pointing at SITE_URL (https://keploy.io/blog)
  instead of the main Organization URL. Imported MAIN_SITE_URL and
  switched worksFor.url to it so Organization identity is consistent
  across every JSON-LD payload on the site.

Comment 4 - AuthorDetailPage E2E regression guard:
  Previous test asserted 'title is defined' which still passes for
  an empty string (the exact bug LIVE-11 was fixing). Tightened to
  require title.trim().length > 0 and title not starting with
  'undefined'. Added a second test that parses every
  script[type=application/ld+json] block and asserts at least one
  Person schema is present in the initial HTML payload — this
  catches any regression that removes the Person schema or moves
  it to a client-hydrated script tag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix(blog): author/reviewer rendering + schema — 4 reported bugs

Reporter flagged 4 issues on 2026-04-14 where WordPress/PublishPress
data was present in the page but was being rendered inconsistently
or not at all, confusing both human readers and AI crawlers.

Problem 1 — Author mismatch (Animesh Pathak vs Alok Kumar)

The GraphQL query for slug pages fetched BOTH `author { node { name } }`
(the WordPress system author who published the post) AND `ppmaAuthorName`
(the PublishPress Multiple Authors display author). The display + schema
correctly used ppmaAuthorName, but the raw `author` field was still
serialized into __NEXT_DATA__, giving AI crawlers a third conflicting
author signal.

Fix: remove `author { node { ...AuthorFields } }` and the AuthorFields
fragment reference from the PostFields fragment in lib/api.ts. The
revision branch also drops the raw author lookup. AuthorMapping.tsx
(which does consume raw `author.node.name`) uses a separate
`getAllAuthors` query that is untouched.

Problem 2 — Reviewer placeholder "Reviewer" in rendered HTML

reviewAuthorDetails arrived in props from getStaticProps but was
being read inside a `useEffect` that called setState. That means
the reviewer name only populated AFTER client hydration — the SSR
HTML shipped with the literal fallback string "Reviewer" in the
Written By / Reviewed By block, which is exactly what AI crawlers
read.

Fix: in both pages/community/[slug].tsx and pages/technology/[slug].tsx,
replace the useState+useEffect pattern with synchronous render-time
computation from props. reviewerNode, reviewAuthorName,
reviewAuthorImageUrl, and reviewAuthorDescription are all derived on
first render so the SSR HTML contains the real reviewer data.

Problem 3 — Author photo shows /blog/images/author.png placeholder

The writer avatar was being extracted from post.content via a regex
against the pp-author-boxes-avatar div, inside a useEffect. Same SSR
timing bug as #2 — initial HTML showed the placeholder, the real
image only swapped in after hydration.

Fix: query `ppmaAuthorImage` directly in the PostFields fragment
(lib/api.ts — the field is already used by getAllAuthors so we know
it exists on the WP schema) and use post.ppmaAuthorImage at render
time. The brittle content-regex is gone. A safe /blog/images/author.png
fallback remains for posts that genuinely have no author image.

Problem 4 — BlogPosting schema missing reviewedBy

The data for the reviewer was sitting in reviewAuthorDetails but
getBlogPostingSchema in lib/structured-data.ts didn't accept a
reviewer parameter at all.

Fix: extend BlogPostingInput with reviewerName / reviewerImage /
reviewerDescription. getBlogPostingSchema emits a `reviewedBy`
Person node when reviewerName is present AND is not the "Reviewer"
placeholder AND is different from the author (a self-reviewed post
is not a useful E-E-A-T signal and AI models discount it). The
Person node includes url (/blog/authors/{slug}), image (avatar),
and description (job title / bio) when available. Also adds an
authorImage field to Person so the author node in the schema
carries the real PublishPress avatar.

Both slug pages now pass the reviewer fields when building the
Article schema. Placeholder detection guards against emitting
schema that contradicts the visible HTML.

Verification after deploy:
  curl -s https://keploy.io/blog/community/<any-post> | \
    python3 -c "import sys, re, json; \
    html = sys.stdin.read(); \
    blocks = re.findall(r'application/ld\+json[^>]*>([^<]+)</script>', html); \
    for b in blocks: \
      d = json.loads(b); \
      if isinstance(d, dict) and d.get('@type') in ('BlogPosting', 'TechArticle', 'Article'): \
        print('author.name:', d.get('author', {}).get('name')); \
        print('author.image:', d.get('author', {}).get('image')); \
        print('reviewedBy:', d.get('reviewedBy', {}).get('name'))"
  # expected: author.name = ppmaAuthorName, author.image = ppmaAuthorImage URL,
  # reviewedBy.name = Neha Gupta (or current reviewer)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix(copilot-review): 4 comments on blog PR #373 round 2

Comment 1 — robots.txt paths missing /blog basePath prefix:
  The Next.js app is mounted under basePath: '/blog' (next.config.js).
  Crawlers request /blog/api/*, /blog/wp/*, /blog/cgi-bin/*, not the
  bare paths. Updated the default User-agent block to prefix every
  Allow/Disallow with /blog so the intended policy actually matches.
  Added an explanatory comment at the top of the block so the next
  editor doesn't revert this. /blog/api/proxy-image remains explicitly
  allowed; all other /blog/api/* remain disallowed.

Comment 2 — vercel.json redirects duplicate next.config.js:
  Copilot noticed (and they are right) that the 26 cross-category
  redirects I added in 1fa4232 already exist in next.config.js's
  redirects() function — verified at lines 74-205. Duplicating them
  in vercel.json creates two sources of truth that can drift. Reverted
  vercel.json back to the 4 original pre-existing redirects (the
  everything-you-need-to-know and regression-testing-tools-rankings
  pairs) which are NOT in next.config.js. next.config.js remains the
  single source of truth for all other slug redirects.

Comment 3 — redirect chain via end-to-end-testing legacy URL:
  I had /blog/community/end-to-end-testing-and-why-do-you-need-it →
  /blog/community/integration-of-e2e-testing-in-a-cicd-pipeline,
  which would have chained through the community→technology redirect
  (also already in next.config.js) for an extra hop. next.config.js
  actually points the legacy URL at /community/end-to-end-testing-guide
  directly. By reverting vercel.json I'm deferring to next.config.js's
  destination, which is the canonical 1-hop path.

Comment 4 — codium-vs-copilot destination conflict:
  I had the malformed-double-path URL pointing at
  /blog/community/cursor-vs-copilot (which may not exist). next.config.js
  already maps the same legacy URL to
  /community/codium-vs-copilot-which-ai-coding-assistant-is-best-for-you,
  which is the real published slug. Reverting vercel.json removes the
  conflict; next.config.js is the single authority.

Net effect: 28 redirect entries removed from vercel.json. They all
remain in next.config.js where they were already defined before this
PR. robots.txt paths now correctly prefixed with /blog basePath.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix(copilot-review): remove unused AuthorFields fragment, repeat blog Disallow rules inside AI-search UA group

Addresses 3 unresolved Copilot review comments on PR #373.

1. lib/api.ts — the `fragment AuthorFields on User` definition was left
   behind in getPostAndMorePosts after the earlier commit removed the
   `author { ...AuthorFields }` field from the PostFields fragment
   (that removal fixed the PPMA-vs-native-author mismatch in
   __NEXT_DATA__). WPGraphQL rejects unused fragment definitions at
   parse time with a validation error, so leaving the stub in would
   500 the PostBySlug query on every request. Removed the fragment
   block entirely.

2. public/robots.txt — per the robots.txt spec, a bot that matches a
   named `User-agent:` group reads rules only from THAT group; it does
   not fall through to `User-agent: *`. That meant OAI-SearchBot,
   PerplexityBot, Claude-SearchBot, etc. were allowed to crawl
   /blog/wp/, /blog/api/, /blog/cgi-bin/ even though the global block
   catches Googlebot. Consolidated all AI-search bots into a single
   named group with the same Disallow rules (/blog/cgi-bin/,
   /blog/wp/, /blog/api/) and an explicit Allow for
   /blog/api/proxy-image so external avatar/image rendering still
   works.

Signed-off-by: Neha Gupta <gneha21@yahoo.in>

* fix(api): move author-field explainer out of the graphql template literal

The PostBySlug fragment had a multi-line # comment explaining why the raw
WP author field is omitted, and the last line referenced "a separate
getAllAuthors query". Real WPGraphQL strips # comments during parsing so
production was fine — but tests/mock-server.js matches queries by substring
and checks query.includes('getAllAuthors') before query.includes('PostBySlug').
So at E2E test time every getPostAndMorePosts call routed to the mock's
allAuthorsResponse branch (has data.posts, no data.post). Slug pages saw
!data.post, returned notFound from getStaticProps, and every post-page
Playwright test waited 15s for data-testid=post-content until the 20min
job cancelled.

Moved the same note to a // JS comment above the template literal so the
query text no longer contains the getAllAuthors substring. Verified on a
clean local build: .next/server/pages/technology/ now has 4 individual
slug HTML files and community/ has 3 — previously 0 and 0 respectively.

Signed-off-by: nehagup <15074229+nehagup@users.noreply.github.com>

* fix(copilot-review): remove dead updatedContent state, apply table-wrap at render time

Addresses 2 Copilot review comments on PR #373 about pages/community/[slug].tsx.

The component declared a useState for updatedContent and a useEffect
that computed a table-wrapped version of post.content, but nothing
ever read updatedContent — PostBody was receiving the raw content
routed through the postBody helper instead. Net result: the responsive
table wrapper was silently disabled on every community post, and an
extra re-render fired on each post change for no functional gain.

Fix (pure cleanup, no behaviour regression — actually restores the
table-wrap behaviour that the original code intended):

1. Removed the updatedContent useState declaration and its useEffect.
2. Removed the postBody helper function that was only doing author-link
   rewrites.
3. Added a top-level transformPostContent(content, ppmaAuthorName)
   pure function that applies both transformations in one synchronous
   pass — wraps tables in <div class="overflow-x-auto"> AND rewrites
   /wp/author/SLUG/ links to /blog/authors/PPMA_NAME/.
4. Pass transformPostContent(post?.content, post?.ppmaAuthorName)
   directly into PostBody at render time. SSR HTML now contains both
   transformations, so AI crawlers see the final transformed content
   in the initial HTML payload (matches the pattern used for the
   author/reviewer fix earlier in this branch).
5. Dropped the unused useState import.

Net effect: one less useState, one less useEffect, one less re-render
on post navigation, and the responsive table wrapper actually ships.

Only pages/community/[slug].tsx has the dead updatedContent pattern —
pages/technology/[slug].tsx only has the postBody author-link helper
and does not table-wrap, so no parallel change is needed there.

Signed-off-by: Neha Gupta <gneha21@yahoo.in>

---------

Signed-off-by: Neha Gupta <gneha21@yahoo.in>
Signed-off-by: nehagup <15074229+nehagup@users.noreply.github.com>
Co-authored-by: Neha Gupta <gneha21@yahoo.in>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: nehagup <15074229+nehagup@users.noreply.github.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
* feat: add anouncement bar ui and update for gittogether

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>

* feat: fix announcement bar on mobile devices, limit it to one single

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>

* fix: address copilot review comments on announcement bar

- Announcements: switch height-sync to useLayoutEffect so --announcement-h
  is written before paint, eliminating the one-frame header/nav overlap flicker
- Announcements: guard ResizeObserver with availability check and rAF-debounce
  so the component works in environments without ResizeObserver support
- Announcements: call setAnnouncementHeight("0px") eagerly in handleDismiss
  so the content gap collapses on the same frame as the bar disappears
- Announcements: store the swipe-dismiss setTimeout id in a ref and clear it
  in a cleanup effect to prevent state updates on unmounted components
- Announcements: make dismiss button visible on all viewports (not lg-only)
  so keyboard/screen-reader users on mobile can dismiss without swiping;
  add pr-12 on mobile to keep content clear of the button
- Marquee: set animationPlayState to undefined (not 'running') when paused
  is false, so the group-hover:paused CSS class still works for pauseOnHover
- Tests: add Playwright spec covering desktop + mobile announcement bar
  behaviour including dismiss, --announcement-h CSS var, swipe gesture,
  and dismiss button accessibility across breakpoints

Co-authored-by: amaan-bhati <amaan.bhati@keploy.io>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>

* fix: harden announcement dismiss reset

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>

* fix: address follow-up announcement review comments

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>

* chore: fix the tests workflow

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>

* chore: address minor content change

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>

---------

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>
Co-authored-by: amaan-bhati <amaan.bhati@keploy.io>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
* added a video banner

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>

* fix(sidebar): layout shift placeholder, reduced-motion video, accessible fallback link

* fix(sidebar): use AD_ITEMS[number] for accurate ad state type

* fix(sidebar): correct fallback image sizes and reduce duplicate video fetches

Co-Authored-By: Dhananjay Aggarwal <dhananjayaggarwal6561@gmail.com>

---------

Signed-off-by: dhananjay6561 <dhananjayaggarwal6561@gmail.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
…ent html (#382)

* feat: fix llm vidibility bug fix, article body appearence

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>

* test: strengthen SSR post body regression assertion

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>

* chore: make nbsp fallback explicit

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>

---------

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
* chore: turn off gitttogether announcement

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>

* test: skip announcement bar suite while feature is disabled

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>

---------

Signed-off-by: amaan-bhati <amaanbhati49@gmail.com>
Signed-off-by: heetmehta18 <heetmehta18125@gmail.com>
Copilot AI review requested due to automatic review settings May 27, 2026 12:01
@HEETMEHTA18 HEETMEHTA18 force-pushed the feat/testimonial-marquee-redesign branch from 729fe03 to 670bf69 Compare May 27, 2026 12:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 106 out of 124 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (2)

vercel.json:1

  • The broad /blog/(.*) header rule also matches /blog/_next/static/*. Depending on how Vercel merges/overrides headers, this can result in conflicting Cache-Control values and downgrade static asset caching from immutable to a shorter TTL. Narrow the /blog/(.*) rule (e.g., exclude /_next/ and /api/), or remove Cache-Control from the broad rule and keep caching scoped to specific asset/page patterns.
    components/testimonials.tsx:1
  • getInitials is currently unused, and FallbackAvatar ignores the provided name. Either remove getInitials to reduce dead code, or use it in the fallback UI (e.g., render initials when the image fails) so the helper has a purpose.

<div className="flex items-center justify-between overflow-visible">
{/* Logo */}
<Link href="/" className="flex items-center gap-2 overflow-hidden z-50 w-[80px]">
<Link href="https://keploy.io/" className="flex items-center gap-2 overflow-hidden z-50 w-[80px]">
Comment on lines +17 to +19
env:
INDEXNOW_HOST: keploy.io
INDEXNOW_KEY: feeeddbe661a4686ba6e4d5cb663b2cb
Comment on lines 27 to 31
<Image
width={2000}
height={1000}
alt={`Cover Image for ${title}`}
src={coverImage?.node.sourceUrl}
Comment on lines +48 to +57
<Link
href={twitterShare}
target="_blank"
rel="noopener noreferrer"
aria-label="Share on X"
className={iconCls}
style={{ backgroundColor: "#000", color: "#fff" }}
>
<FaXTwitter />
</Link>
@HEETMEHTA18 HEETMEHTA18 deleted the feat/testimonial-marquee-redesign branch May 29, 2026 11:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants