feat(blog): add blog system, sitemap automation, and cookie fixes#131
feat(blog): add blog system, sitemap automation, and cookie fixes#131omsherikar merged 6 commits intomainfrom
Conversation
- Replace Case Studies with full Blog system (BlogPage, BlogPostPage) - Add freeform body parser (parseBody) so authors paste raw text - Bento grid for featured posts + masonry grid for regular posts - Functional search bar across all posts - Add 8 blog posts across Product, Case Study, Engineering, Industry Analysis, Developer Guide categories - Add automated sitemap generation via scripts/generate-sitemap.js (prebuild hook) - Fix cookie consent: gate Vercel Analytics behind analytics preference - Simplify CookieConsent, CookieManager, useCookieConsent - Add status badge to Footer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. OpenSSF Scorecard
Scanned Files
|
|
👋 Thanks for opening this pull request! A maintainer will review it soon. Please make sure all CI checks pass. |
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 4 minutes and 16 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThe PR replaces the case-study section with a blog system: removes case study data/components, adds blog data/components and routing, introduces sitemap generation and prebuild integration, updates prerendering to include blog posts, and simplifies cookie-consent analytics handling. Changes
Sequence DiagramsequenceDiagram
participant User
participant Router
participant BlogPage
participant BlogPostPage
participant PostsModule as posts.ts
User->>Router: GET /blog
Router->>BlogPage: render
BlogPage->>PostsModule: fetch all posts
PostsModule-->>BlogPage: return blogPosts[]
BlogPage-->>User: show listing (search/filter)
User->>BlogPage: click post card
Router->>BlogPostPage: navigate /blog/:slug
BlogPostPage->>PostsModule: getBlogPostBySlug(slug)
PostsModule-->>BlogPostPage: return post
BlogPostPage->>BlogPostPage: parseBody(post.body)
BlogPostPage-->>User: render post content
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Replaces the existing Case Studies content with a full Blog system, adds automated sitemap generation, and updates cookie consent handling to gate Vercel Analytics behind analytics consent.
Changes:
- Added Blog listing + Blog post detail pages, plus a post data source and lightweight body parser.
- Added a prebuild sitemap generator to automatically include blog post routes.
- Simplified cookie consent flow and gated Vercel Analytics events based on stored cookie preferences.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/hooks/useCookieConsent.ts | Simplifies consent persistence logic and removes in-hook analytics toggling. |
| src/data/posts.ts | Adds blog post data + parseBody() to render freeform post content. |
| src/data/caseStudies.ts | Removes Case Studies data source. |
| src/components/NavigationBar.tsx | Updates nav link from Case Studies to Blog. |
| src/components/Footer.tsx | Updates footer link from Case Studies/Examples to Blog. |
| src/components/CookiePreferencesModal.tsx | Tweaks consent modal styling (info box colors). |
| src/components/CookieManager.tsx | Simplifies banner handlers and wiring into savePreferences. |
| src/components/CookieConsent.tsx | Removes explicit reject callback and routes reject into onAccept w/ disabled prefs. |
| src/components/CaseStudyDetailPage.tsx | Removes Case Study detail page. |
| src/components/CaseStudiesPage.tsx | Removes Case Studies listing page. |
| src/components/BlogPostPage.tsx | Adds blog post detail rendering (icons + parsed body sections). |
| src/components/BlogPage.tsx | Adds blog listing page w/ featured bento grid, masonry list, and search. |
| src/App.tsx | Switches routes from /case-studies to /blog and gates Analytics with beforeSend. |
| scripts/generate-sitemap.js | Adds automated sitemap generation from blog post slugs. |
| public/sitemap.xml | Replaces static sitemap with generated sitemap output including blog URLs. |
| package.json | Adds sitemap dependency and prebuild hook to generate sitemap. |
| package-lock.json | Locks sitemap and transitive dependencies (incl. engine constraints). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "devDependencies": { | ||
| "@sparticuz/chromium": "^143.0.4", | ||
| "prettier": "^3.6.2", | ||
| "puppeteer-core": "^24.40.0" | ||
| "puppeteer-core": "^24.40.0", | ||
| "sitemap": "^9.0.1" | ||
| }, | ||
| "scripts": { | ||
| "start": "react-scripts start", | ||
| "prebuild": "node scripts/generate-sitemap.js", | ||
| "build": "react-scripts build", |
There was a problem hiding this comment.
The added sitemap@^9.0.1 devDependency declares an engines requirement of node >=20.19.5 (see package-lock), but this repo’s CI matrix includes Node 16.x and 18.x. This is likely to break npm ci and/or npm run build in those environments because prebuild runs the sitemap generator. Consider pinning sitemap to a version that supports the project’s supported Node versions, or updating the supported Node/CI matrix accordingly (and documenting it).
| const staticRoutes = [ | ||
| { url: '/', changefreq: 'weekly', priority: 1.0 }, | ||
| { url: '/blog', changefreq: 'weekly', priority: 0.9 }, | ||
| { url: '/pricing', changefreq: 'monthly', priority: 0.9 }, | ||
| { url: '/about', changefreq: 'monthly', priority: 0.6 }, | ||
| { url: '/changelog', changefreq: 'weekly', priority: 0.7 }, | ||
| { url: '/security', changefreq: 'monthly', priority: 0.5 }, | ||
| { url: '/privacy-policy', changefreq: 'yearly', priority: 0.3 }, | ||
| { url: '/terms-of-service',changefreq: 'yearly', priority: 0.3 }, |
There was a problem hiding this comment.
/pricing is included as a static sitemap route, but the app routes in src/App.tsx do not define a /pricing page (pricing appears to be a section on the landing page). This will publish a broken URL in sitemap.xml and hurt SEO/crawl quality. Either remove /pricing from the sitemap or add a real /pricing route/page.
| textBuffer.push(line); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
parseBody drops any trailing code block if the input ends while insideCode is still true (i.e., an unclosed ``` fence). That results in losing content and can render posts incomplete. Consider flushing codeLines at EOF when `insideCode` is still true (either as a `code` section or as a paragraph fallback).
| if (insideCode && codeLines.length > 0) { | |
| sections.push({ type: 'code', text: codeLines.join('\n') }); | |
| } |
| function getIcon(industry: string) { | ||
| const attr = { | ||
| stroke: 'rgba(0,0,0,0.45)', | ||
| strokeWidth: 1.5, | ||
| fill: 'none' as const, | ||
| strokeLinecap: 'round' as const, | ||
| strokeLinejoin: 'round' as const, | ||
| }; | ||
| const lower = industry.toLowerCase(); |
There was a problem hiding this comment.
getIcon() is implemented locally in this file but appears to be essentially duplicated in BlogPage.tsx. Keeping two copies of the same category→SVG mapping will likely drift over time. Consider extracting this into a shared utility/component (e.g., src/components/blog/blogIcons.tsx) and reusing it in both pages.
| // Minimal line-art SVG icons keyed by industry | ||
| function getIcon(industry: string, size = 64) { | ||
| const attr = { | ||
| stroke: 'rgba(0,0,0,0.45)', | ||
| strokeWidth: 1.5, | ||
| fill: 'none' as const, | ||
| strokeLinecap: 'round' as const, | ||
| strokeLinejoin: 'round' as const, | ||
| }; |
There was a problem hiding this comment.
getIcon() is implemented locally here and is essentially duplicated in BlogPostPage.tsx. To avoid mismatches (e.g., different icon selection rules or sizes per category), consider extracting a shared icon renderer/util and importing it in both files.
- Remove stale /case-studies route - Add /blog, /changelog, /security to static pre-render list - Dynamically read blog slugs from posts.ts so every blog post gets a pre-rendered index.html for crawlers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (3)
package.json (1)
43-48: Avoid regex-scanning raw post source for sitemap slugs.
prebuildnow hard-depends onscripts/generate-sitemap.js, and that script extracts everyslug:match from the rawsrc/data/posts.tstext. With the new freeform post bodies, a pasted code/frontmatter snippet containingslug: "..."can generate bogus sitemap URLs. Please switch sitemap generation to a structured export/manifest rather than scanning the source file.src/components/BlogPage.tsx (1)
7-195: ExtractgetIconinto a shared module to avoid drift.This icon-selection logic is duplicated in
src/components/BlogPostPage.tsx(Line 7 onward). Keeping one shared helper/component will prevent mismatched icon behavior over time.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/BlogPage.tsx` around lines 7 - 195, The getIcon function is duplicated; extract the getIcon implementation into a single shared module (e.g., export function getIcon(...) from a new file) and replace the inline copies in BlogPage.tsx and BlogPostPage.tsx with an import of that shared getIcon; ensure the new module exports the same named function signature and preserves the default size parameter and returned JSX so both files call getIcon(industry, size) unchanged, update imports in both components, and remove the duplicated function bodies.scripts/generate-sitemap.js (1)
13-15: Harden slug extraction to avoid false sitemap URLs.Line 13 currently matches any
slug:token in the file, including potential future prose/code snippets inbodycontent. Anchor matching to property lines and dedupe before route generation.Proposed fix
-const slugMatches = [...postsFile.matchAll(/slug:\s*['"]([^'"]+)['"]/g)]; -const blogSlugs = slugMatches.map(m => m[1]); +const slugMatches = [ + ...postsFile.matchAll(/^\s*slug:\s*['"]([^'"]+)['"],?\s*$/gm), +]; +const blogSlugs = [...new Set(slugMatches.map(([, slug]) => slug.trim()))];Also applies to: 28-32
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/generate-sitemap.js` around lines 13 - 15, The slug extraction currently uses postsFile.matchAll(/slug:\s*['"]([^'"]+)['"]/g) which can match `slug:` occurrences inside body text; update the regex to anchor to the start of a property line (e.g. use /(?:^|\n)\s*slug:\s*['"]([^'"]+)['"]/gm or /^\s*slug:\s*['"]([^'"]+)['"]/gm) when building slugMatches, then map to blogSlugs as before but remove duplicates (e.g. Array.from(new Set(blogSlugs))) so subsequent route generation uses only true frontmatter slug lines and unique slugs; apply the same anchored regex + dedupe logic where slugs are extracted around lines 28-32 as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/App.tsx`:
- Around line 101-116: The new Route entries for Blog (Route path="/blog"
element={<PageLayout><BlogPage/></PageLayout>}) and BlogPost (Route
path="/blog/:slug" element={<PageLayout mainClassName="pt-0
sm:pt-0"><BlogPostPage/></PageLayout>}) remove the legacy /case-studies entry
points and will 404 old links; add compatibility routes that either render the
same components or issue client redirects for the old paths (add Route for
path="/case-studies" that maps to the same PageLayout+BlogPage and Route for
path="/case-studies/:slug" that maps to the same PageLayout+BlogPostPage or use
a Navigate redirect to the corresponding /blog routes) so bookmarks/backlinks
remain valid during migration.
In `@src/components/BlogPage.tsx`:
- Around line 1-427: Prettier format checks are failing for BlogPage.tsx; run
Prettier and reformat the file (ensuring the exported BlogPage component and
related functions like getIcon, getBentoSpan, BentoCard, RegularCard remain
unchanged functionally) so it passes CI --check; typically run your project's
formatter command (e.g., npx prettier --write src/components/BlogPage.tsx or
yarn prettier:fix) and commit the modified file.
- Around line 303-310: In matchPost, normalize the incoming search string by
trimming whitespace before lowercasing so leading/trailing spaces don't cause
false negatives; replace the current q assignment (which uses
query.toLowerCase()) with one that does query.trim().toLowerCase() and use that
q in the existing checks (keep references to p.title, p.industry, p.summary, and
p.tags.some(...) unchanged); optionally short-circuit if q is empty to avoid
unnecessary work.
- Around line 213-220: The clickable non-semantic motion.div should be replaced
with a semantic, keyboard-accessible motion.button: change the motion.div
element (the one using initial/whileInView/viewport/transition, onClick={() =>
navigate(`/blog/${post.slug}`)}, className and props like index and wide) to
motion.button, remove any invalid button-only props if present, ensure the
navigate(`/blog/${post.slug}`) call remains in the onClick, and preserve the
animation props (initial, whileInView, viewport, transition) and className so
the visual behavior is identical while gaining native keyboard focus and
activation support.
In `@src/components/BlogPostPage.tsx`:
- Around line 1-319: The file src/components/BlogPostPage.tsx fails Prettier
formatting in CI; run Prettier on this file (e.g. prettier --write
src/components/BlogPostPage.tsx or your project’s prettier/npm script) to fix
whitespace/format issues and re-commit so the CI --check passes; focus on
formatting the exported BlogPostPage component and helper functions like getIcon
and renderSection, then re-run the Prettier check before pushing.
In `@src/data/posts.ts`:
- Around line 1-734: Prettier is flagging formatting drift in this file; run the
project's Prettier formatter against this file (or the whole repo) and commit
the resulting changes to satisfy CI. Locate the top-level exports (parseBody,
blogPosts array, and getBlogPostBySlug) to confirm Prettier's changes didn't
alter logic; run your configured command (e.g., npm/yarn prettier --write or the
repo's format script) and add the formatted file to the PR, then push so the CI
Prettier check passes.
- Around line 74-93: The loop in posts.ts can end with insideCode === true which
loses accumulated codeLines; update the EOF handling so any unterminated code
block is emitted before returning: after the for-loop (before return sections)
detect if insideCode is true and push a section like sections.push({ type:
'code', text: codeLines.join('\n') }) (or modify flushText to handle both
textBuffer and codeLines), then clear state so no content is dropped; reference
the variables insideCode, codeLines, sections and the flushText helper when
making the change.
In `@src/hooks/useCookieConsent.ts`:
- Around line 51-68: The hook's savePreferences only updates local state and
localStorage for that hook instance (savePreferences in useCookieConsent) so
separate consumers (Footer.tsx and CookieManager.tsx) don't sync; fix by making
the cookie state shared across components either by (A) lifting state into a
CookieConsentProvider: move preferences, hasConsent, setPreferences,
setHasConsent, and savePreferences into a React context/provider and have
useCookieConsent read/write from that context so all consumers subscribe to the
same state, or (B) if you prefer minimal change, broadcast updates inside
savePreferences via window.dispatchEvent(new
CustomEvent('cookie-preferences-changed', { detail: newPreferences })) and add a
window.addEventListener('cookie-preferences-changed', ...) in useCookieConsent
to update setPreferences and setHasConsent when events arrive; update Footer.tsx
and CookieManager.tsx to use the provider or rely on the event so banner/modal
reflect changes immediately.
---
Nitpick comments:
In `@scripts/generate-sitemap.js`:
- Around line 13-15: The slug extraction currently uses
postsFile.matchAll(/slug:\s*['"]([^'"]+)['"]/g) which can match `slug:`
occurrences inside body text; update the regex to anchor to the start of a
property line (e.g. use /(?:^|\n)\s*slug:\s*['"]([^'"]+)['"]/gm or
/^\s*slug:\s*['"]([^'"]+)['"]/gm) when building slugMatches, then map to
blogSlugs as before but remove duplicates (e.g. Array.from(new Set(blogSlugs)))
so subsequent route generation uses only true frontmatter slug lines and unique
slugs; apply the same anchored regex + dedupe logic where slugs are extracted
around lines 28-32 as well.
In `@src/components/BlogPage.tsx`:
- Around line 7-195: The getIcon function is duplicated; extract the getIcon
implementation into a single shared module (e.g., export function getIcon(...)
from a new file) and replace the inline copies in BlogPage.tsx and
BlogPostPage.tsx with an import of that shared getIcon; ensure the new module
exports the same named function signature and preserves the default size
parameter and returned JSX so both files call getIcon(industry, size) unchanged,
update imports in both components, and remove the duplicated function bodies.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7cacdfa9-f278-4d88-ac95-74d55938e879
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (16)
package.jsonpublic/sitemap.xmlscripts/generate-sitemap.jssrc/App.tsxsrc/components/BlogPage.tsxsrc/components/BlogPostPage.tsxsrc/components/CaseStudiesPage.tsxsrc/components/CaseStudyDetailPage.tsxsrc/components/CookieConsent.tsxsrc/components/CookieManager.tsxsrc/components/CookiePreferencesModal.tsxsrc/components/Footer.tsxsrc/components/NavigationBar.tsxsrc/data/caseStudies.tssrc/data/posts.tssrc/hooks/useCookieConsent.ts
💤 Files with no reviewable changes (3)
- src/components/CaseStudiesPage.tsx
- src/components/CaseStudyDetailPage.tsx
- src/data/caseStudies.ts
| <motion.div | ||
| initial={{ opacity: 0, y: 30 }} | ||
| whileInView={{ opacity: 1, y: 0 }} | ||
| viewport={{ once: true }} | ||
| transition={{ duration: 0.5, delay: index * 0.08 }} | ||
| onClick={() => navigate(`/blog/${post.slug}`)} | ||
| className={`group rounded-2xl overflow-hidden cursor-pointer flex flex-col ${wide ? 'md:col-span-2' : 'md:col-span-1'}`} | ||
| > |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "BlogPage.tsx" | head -20Repository: Refactron-ai/Refactron_Website
Length of output: 103
🏁 Script executed:
wc -l ./src/components/BlogPage.tsxRepository: Refactron-ai/Refactron_Website
Length of output: 107
🏁 Script executed:
sed -n '210,225p' ./src/components/BlogPage.tsxRepository: Refactron-ai/Refactron_Website
Length of output: 698
🏁 Script executed:
sed -n '255,270p' ./src/components/BlogPage.tsxRepository: Refactron-ai/Refactron_Website
Length of output: 686
🏁 Script executed:
sed -n '200,235p' ./src/components/BlogPage.tsxRepository: Refactron-ai/Refactron_Website
Length of output: 1345
🏁 Script executed:
sed -n '245,280p' ./src/components/BlogPage.tsxRepository: Refactron-ai/Refactron_Website
Length of output: 1262
🏁 Script executed:
head -50 ./src/components/BlogPage.tsxRepository: Refactron-ai/Refactron_Website
Length of output: 1615
🏁 Script executed:
grep -n "motion\." ./src/components/BlogPage.tsx | head -30Repository: Refactron-ai/Refactron_Website
Length of output: 253
🏁 Script executed:
grep -n "role\|aria-\|tabIndex\|onKeyDown\|onKeyUp" ./src/components/BlogPage.tsxRepository: Refactron-ai/Refactron_Website
Length of output: 56
🏁 Script executed:
web_search
Does Framer Motion support motion.button component?Repository: Refactron-ai/Refactron_Website
Length of output: 167
🌐 Web query:
Framer Motion motion.button component support
💡 Result:
Yes—motion.button is supported.
Motion (formerly Framer Motion) provides a motion. version of every HTML/SVG element, so you can use <motion.button> as a drop-in replacement for <button> and add animation/gesture props like initial, animate, whileHover, whileTap, exit, etc. [1], [2]
Example:
import { motion } from "motion/react"
export function MyButton() {
return (
<motion.button
initial={{ scale: 0 }}
animate={{ scale: 1 }}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
Click
</motion.button>
)
}[1]
If you meant a custom Button component (not a native <button>), wrap it with motion.create() (it must forward its ref and accept a style prop). [2]
Sources: [1] [2]
Replace non-semantic clickable divs with keyboard-accessible button elements.
Lines 213 and 257 use motion.div with onClick handlers, which are not keyboard-operable. This blocks keyboard-only and screen reader users from navigating to posts. Replace with motion.button to provide native keyboard support (Tab focus and Enter/Space activation).
Proposed fix
- <motion.div
+ <motion.button
+ type="button"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.08 }}
onClick={() => navigate(`/blog/${post.slug}`)}
- className={`group rounded-2xl overflow-hidden cursor-pointer flex flex-col ${wide ? 'md:col-span-2' : 'md:col-span-1'}`}
+ className={`group rounded-2xl overflow-hidden cursor-pointer flex flex-col text-left ${wide ? 'md:col-span-2' : 'md:col-span-1'}`}
>
@@
- </motion.div>
+ </motion.button>- <motion.div
+ <motion.button
+ type="button"
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.4, delay: index * 0.07 }}
onClick={() => navigate(`/blog/${post.slug}`)}
- className="group rounded-2xl overflow-hidden cursor-pointer flex flex-col bg-[`#111111`] hover:bg-[`#161616`] transition-colors break-inside-avoid mb-4"
+ className="group rounded-2xl overflow-hidden cursor-pointer flex flex-col bg-[`#111111`] hover:bg-[`#161616`] transition-colors break-inside-avoid mb-4 text-left w-full"
>
@@
- </motion.div>
+ </motion.button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/BlogPage.tsx` around lines 213 - 220, The clickable
non-semantic motion.div should be replaced with a semantic, keyboard-accessible
motion.button: change the motion.div element (the one using
initial/whileInView/viewport/transition, onClick={() =>
navigate(`/blog/${post.slug}`)}, className and props like index and wide) to
motion.button, remove any invalid button-only props if present, ensure the
navigate(`/blog/${post.slug}`) call remains in the onClick, and preserve the
animation props (initial, whileInView, viewport, transition) and className so
the visual behavior is identical while gaining native keyboard focus and
activation support.
| const matchPost = (p: BlogPost) => { | ||
| const q = query.toLowerCase(); | ||
| return ( | ||
| p.title.toLowerCase().includes(q) || | ||
| p.industry.toLowerCase().includes(q) || | ||
| p.summary.toLowerCase().includes(q) || | ||
| p.tags.some(t => t.toLowerCase().includes(q)) | ||
| ); |
There was a problem hiding this comment.
Normalize the search query before matching.
Line 304 uses the raw query for filtering. Leading/trailing spaces can produce false negatives even when isSearching is true.
Proposed fix
- const q = query.toLowerCase();
+ const q = query.trim().toLowerCase();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const matchPost = (p: BlogPost) => { | |
| const q = query.toLowerCase(); | |
| return ( | |
| p.title.toLowerCase().includes(q) || | |
| p.industry.toLowerCase().includes(q) || | |
| p.summary.toLowerCase().includes(q) || | |
| p.tags.some(t => t.toLowerCase().includes(q)) | |
| ); | |
| const matchPost = (p: BlogPost) => { | |
| const q = query.trim().toLowerCase(); | |
| return ( | |
| p.title.toLowerCase().includes(q) || | |
| p.industry.toLowerCase().includes(q) || | |
| p.summary.toLowerCase().includes(q) || | |
| p.tags.some(t => t.toLowerCase().includes(q)) | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/BlogPage.tsx` around lines 303 - 310, In matchPost, normalize
the incoming search string by trimming whitespace before lowercasing so
leading/trailing spaces don't cause false negatives; replace the current q
assignment (which uses query.toLowerCase()) with one that does
query.trim().toLowerCase() and use that q in the existing checks (keep
references to p.title, p.industry, p.summary, and p.tags.some(...) unchanged);
optionally short-circuit if q is empty to avoid unnecessary work.
| for (const line of lines) { | ||
| if (line.trimStart().startsWith('```')) { | ||
| if (!insideCode) { | ||
| flushText(); | ||
| insideCode = true; | ||
| codeLines = []; | ||
| } else { | ||
| sections.push({ type: 'code', text: codeLines.join('\n') }); | ||
| insideCode = false; | ||
| codeLines = []; | ||
| } | ||
| } else if (insideCode) { | ||
| codeLines.push(line); | ||
| } else { | ||
| textBuffer.push(line); | ||
| } | ||
| } | ||
|
|
||
| flushText(); | ||
| return sections; |
There was a problem hiding this comment.
Unterminated code fences drop content at EOF.
If input ends while insideCode is true, accumulated codeLines are never emitted. That silently loses author content.
Proposed fix
for (const line of lines) {
@@
}
+ if (insideCode) {
+ sections.push({ type: 'code', text: codeLines.join('\n') });
+ }
flushText();
return sections;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (const line of lines) { | |
| if (line.trimStart().startsWith('```')) { | |
| if (!insideCode) { | |
| flushText(); | |
| insideCode = true; | |
| codeLines = []; | |
| } else { | |
| sections.push({ type: 'code', text: codeLines.join('\n') }); | |
| insideCode = false; | |
| codeLines = []; | |
| } | |
| } else if (insideCode) { | |
| codeLines.push(line); | |
| } else { | |
| textBuffer.push(line); | |
| } | |
| } | |
| flushText(); | |
| return sections; | |
| for (const line of lines) { | |
| if (line.trimStart().startsWith(' |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/data/posts.ts` around lines 74 - 93, The loop in posts.ts can end with
insideCode === true which loses accumulated codeLines; update the EOF handling
so any unterminated code block is emitted before returning: after the for-loop
(before return sections) detect if insideCode is true and push a section like
sections.push({ type: 'code', text: codeLines.join('\n') }) (or modify flushText
to handle both textBuffer and codeLines), then clear state so no content is
dropped; reference the variables insideCode, codeLines, sections and the
flushText helper when making the change.
| const savePreferences = useCallback((newPreferences: CookiePreferences) => { | ||
| try { | ||
| localStorage.setItem( | ||
| 'cookie-preferences', | ||
| JSON.stringify(newPreferences) | ||
| ); | ||
| localStorage.setItem('cookie-consent', 'true'); | ||
| localStorage.setItem('cookie-consent-date', new Date().toISOString()); | ||
|
|
||
| // Trigger analytics based on preferences | ||
| handleAnalyticsConsent(newPreferences.analytics); | ||
| setPreferences(newPreferences); | ||
| setHasConsent(true); | ||
|
|
||
| return true; | ||
| } catch (error) { | ||
| console.error('Error saving cookie preferences:', error); | ||
| return false; | ||
| } | ||
| }, | ||
| [handleAnalyticsConsent] | ||
| ); | ||
| return true; | ||
| } catch (error) { | ||
| console.error('Error saving cookie preferences:', error); | ||
| return false; | ||
| } | ||
| }, []); |
There was a problem hiding this comment.
useCookieConsent() is not synchronized across components.
savePreferences updates this hook instance plus localStorage only. Since Footer.tsx Line 61 and CookieManager.tsx Lines 7-8 each call useCookieConsent() separately, saving from one surface does not update the other in the same tab. The banner can stay visible after saving from the footer modal, and the footer can reopen with stale values after accepting from the banner. This state needs to live in a shared provider or broadcast an explicit in-tab change event.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/hooks/useCookieConsent.ts` around lines 51 - 68, The hook's
savePreferences only updates local state and localStorage for that hook instance
(savePreferences in useCookieConsent) so separate consumers (Footer.tsx and
CookieManager.tsx) don't sync; fix by making the cookie state shared across
components either by (A) lifting state into a CookieConsentProvider: move
preferences, hasConsent, setPreferences, setHasConsent, and savePreferences into
a React context/provider and have useCookieConsent read/write from that context
so all consumers subscribe to the same state, or (B) if you prefer minimal
change, broadcast updates inside savePreferences via window.dispatchEvent(new
CustomEvent('cookie-preferences-changed', { detail: newPreferences })) and add a
window.addEventListener('cookie-preferences-changed', ...) in useCookieConsent
to update setPreferences and setHasConsent when events arrive; update Footer.tsx
and CookieManager.tsx to use the provider or rely on the event so banner/modal
reflect changes immediately.
- Remove /pricing (section anchor, not a real route) - Remove /status (internal page, not for indexing) - Add /status to prerender exclusion - Regenerate sitemap with correct 7 static + 8 blog routes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
scripts/prerender.js (1)
18-25: Consider extracting shared slug-extraction logic.This slug extraction code is duplicated verbatim in
scripts/generate-sitemap.js(lines 8-14). Extracting this to a shared utility (e.g.,scripts/lib/extractBlogSlugs.js) would improve maintainability and ensure both scripts stay synchronized if the posts.ts format changes.♻️ Proposed shared utility
Create
scripts/lib/extractBlogSlugs.js:const fs = require('fs'); const path = require('path'); function extractBlogSlugs() { const postsFile = fs.readFileSync( path.join(__dirname, '../../src/data/posts.ts'), 'utf-8' ); return [...postsFile.matchAll(/slug:\s*['"]([^'"]+)['"]/g)].map(m => m[1]); } module.exports = { extractBlogSlugs };Then in both
prerender.jsandgenerate-sitemap.js:-const postsFile = fs.readFileSync( - path.join(__dirname, '../src/data/posts.ts'), - 'utf-8' -); -const blogSlugs = [...postsFile.matchAll(/slug:\s*['"]([^'"]+)['"]/g)].map( - m => m[1] -); +const { extractBlogSlugs } = require('./lib/extractBlogSlugs'); +const blogSlugs = extractBlogSlugs();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/prerender.js` around lines 18 - 25, Duplicate slug-extraction logic exists in scripts/prerender.js and scripts/generate-sitemap.js; extract it to a shared utility (e.g., create scripts/lib/extractBlogSlugs.js exporting a function named extractBlogSlugs) that reads ../src/data/posts.ts and returns the slug array, then replace the inline regex blocks in prerender.js and generate-sitemap.js to import/require and call extractBlogSlugs() so both scripts use the single shared implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@scripts/prerender.js`:
- Around line 18-25: Duplicate slug-extraction logic exists in
scripts/prerender.js and scripts/generate-sitemap.js; extract it to a shared
utility (e.g., create scripts/lib/extractBlogSlugs.js exporting a function named
extractBlogSlugs) that reads ../src/data/posts.ts and returns the slug array,
then replace the inline regex blocks in prerender.js and generate-sitemap.js to
import/require and call extractBlogSlugs() so both scripts use the single shared
implementation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Preserve backward compatibility for bookmarks, backlinks, and indexed pages by redirecting /case-studies → /blog and /case-studies/:slug → /blog Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary by CodeRabbit
New Features
Updates
Removed