Skip to content

feat:DR-7521 blog post#7588

Open
carlagn wants to merge 20 commits intomainfrom
feat/DR-7521-blog-post
Open

feat:DR-7521 blog post#7588
carlagn wants to merge 20 commits intomainfrom
feat/DR-7521-blog-post

Conversation

@carlagn
Copy link
Contributor

@carlagn carlagn commented Mar 4, 2026

⚠️ dependent on #7586 to merge

Summary by CodeRabbit

  • New Features

    • Redesigned blog home and post layouts: two-column content-first view, featured post card, badges, author avatars, share buttons, newsletter subscription, and a right-column "On this page" TOC with active-section highlighting.
    • Pre-rendered page generation for blog posts.
  • Improvements

    • Added optional frontmatter fields for author source, meta title/description, and description.
    • Updated typography, global blog background, card/grid layout, and improved pagination with ellipsis-style navigation.
  • Data

    • Added public social icons dataset for site components.

@vercel
Copy link

vercel bot commented Mar 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blog Ready Ready Preview, Comment Mar 6, 2026 5:01pm
docs Ready Ready Preview, Comment Mar 6, 2026 5:01pm
eclipse Ready Ready Preview, Comment Mar 6, 2026 5:01pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Added optional frontmatter fields to blog schema; redesigned blog home and post to content-first two-column layouts; removed hero image rendering; added share buttons, newsletter block, generateStaticParams, InlineTOC active highlighting, variable Mona Sans fonts, socialIcons data and a package export; pagination and CSS tweaks.

Changes

Cohort / File(s) Summary
Blog Schema & Config
apps/blog/source.config.ts
Added optional frontmatter fields: authorSrc, metaDescription, metaTitle, description; normalized string quoting.
Post Detail Page
apps/blog/src/app/(blog)/[slug]/page.tsx
Rewrote page to content-first two-column layout; removed hero image logic; added back-link, structured header, multi-author/avatar support, share buttons, newsletter block, right-column InlineTOC, and exported generateStaticParams.
Blog Home Page
apps/blog/src/app/(blog)/page.tsx
Updated listing: featured first-item, sliced grid for remaining posts, added badges, avatar support, description/metaDescription fallbacks, date normalization, and UI sizing/styling updates.
Blog Grid Component
apps/blog/src/components/BlogGrid.tsx
Added authorSrc? and badge? to BlogCardItem; replaced URL-synced pagination with client-side state and new PaginationWithEllipsis; added featured layout, optional right-side images, avatar/badge rendering, and pagination UI changes.
Inline TOC
packages/eclipse/src/components/inline-toc.tsx
Added IntersectionObserver-based active-item tracking, item flattening, passed activeId to TOC items, and accepted className prop for container styling.
Pagination Styling
packages/eclipse/src/components/pagination.tsx
Added cursor-pointer class to pagination link anchors.
Typography & Globals
packages/eclipse/src/styles/fonts.css, packages/eclipse/src/styles/globals.css
Replaced multiple @font-face entries with Mona Sans VF variable-font declarations; updated CSS vars to reference Mona Sans VF; added .stretch-display utility.
Blog Styling & Layout
apps/blog/src/app/global.css, apps/blog/src/app/layout.tsx
Added .bg-blog, .landing-h1, .newsletter-bg rules; updated body classes to include bg-blog and pt-24; standardized import quoting.
UI Exports & Social Data
packages/ui/package.json, packages/ui/src/data/footer.ts
Added package export path "./data/*": "./src/data/*.ts" and new socialIcons export (Discord, Twitter, YouTube, GitHub).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and overly broad. 'feat:DR-7521 blog post' lacks specificity about what aspect of the blog was changed—it could refer to styling, layout, features, or infrastructure. A scanning developer couldn't understand the primary change without reading the full PR. Revise to be more specific about the main change, such as 'feat: redesign blog post layout with two-column structure' or 'feat: add blog post metadata fields and TOC integration' to clearly convey the primary innovation.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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


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

@argos-ci
Copy link

argos-ci bot commented Mar 4, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ⚠️ Changes detected (Review) 1 removed Mar 6, 2026, 5:07 PM

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (4)
packages/ui/package.json (1)

11-11: Prefer explicit data exports over wildcard API expansion.

Line 11 makes every future file in src/data publicly importable, which can create accidental semver commitments.

♻️ Suggested export map adjustment
-    "./data/*": "./src/data/*.ts",
+    "./data/footer": "./src/data/footer.ts",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/package.json` at line 11, The export map currently exposes
"./data/*" -> "./src/data/*.ts", which expands every future file under src/data
and risks accidental public API. Replace the wildcard export in package.json by
either (a) listing explicit exports for each data module you intend to publish
(e.g., "./data/foo": "./src/data/foo.ts", "./data/bar": "./src/data/bar.ts") or
(b) create a single barrel entry (e.g., "./data": "./src/data/index.ts") that
re-exports only the stable symbols, and remove the "./data/*": "./src/data/*.ts"
entry to avoid implicit semver commitments.
apps/blog/src/components/BlogGrid.tsx (1)

19-20: badge is typed as a string but rendered like a boolean flag.

Line 85 always renders "Release" even when post.badge contains a different value. Either render post.badge or change the field type to boolean to match intent.

Proposed fix (if badge text is dynamic)
                   {post.badge && (
-                    <Badge color="success" label="Release" className="w-min" />
+                    <Badge color="success" label={post.badge} className="w-min" />
                   )}

Also applies to: 84-86

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/components/BlogGrid.tsx` around lines 19 - 20, The prop type
for badge on the Post object (badge?: string | null) is inconsistent with how
it's rendered in BlogGrid (post.badge currently causes the UI to always show the
literal "Release"); either change the type to a boolean (e.g., badge?: boolean)
and update rendering logic in the BlogGrid component to conditionally render the
hardcoded "Release" when true, or keep badge as string and update the rendering
to display post.badge value (and handle null/undefined) where the current
"Release" label is output; locate uses of post.badge in BlogGrid.tsx and adjust
the type and render logic to match the chosen approach.
packages/eclipse/src/styles/fonts.css (1)

2-13: Add font-display: swap to reduce FOIT on slow networks.

Both @font-face blocks omit font-display; adding swap improves perceived performance and keeps content readable during font load.

💡 Proposed fix
 `@font-face` {
     font-family: "Mona Sans VF";
@@
     font-style: normal;
     font-optical-sizing: auto;
+    font-display: swap;
 }
@@
 `@font-face` {
     font-family: "Mona Sans VF";
@@
     font-style: italic;
     font-optical-sizing: auto;
+    font-display: swap;
 }

Also applies to: 15-26

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/eclipse/src/styles/fonts.css` around lines 2 - 13, The `@font-face`
declaration for font-family "Mona Sans VF" is missing a font-display setting
which can cause FOIT; add font-display: swap to each `@font-face` block (both
entries for "Mona Sans VF") so browsers show fallback text immediately and swap
in the webfont when ready, keeping the rest of the descriptors (src,
font-weight, font-stretch, font-style, font-optical-sizing) unchanged.
packages/eclipse/src/styles/globals.css (1)

743-745: Prefer font-stretch over raw font-variation-settings for width utility.

For the width axis utility, font-stretch is more idiomatic and less likely to override other variation settings set elsewhere.

💡 Proposed fix
 .stretch-display {
-        font-variation-settings: "wdth" 125;
+        font-stretch: 125%;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/eclipse/src/styles/globals.css` around lines 743 - 745, Replace the
raw variation setting in the .stretch-display rule: remove
font-variation-settings: "wdth" 125; and use the idiomatic property
font-stretch: 125%; (you can keep a font-variation-settings fallback only if you
explicitly need to target a specific variable font instance, but prefer
font-stretch for the width axis). Ensure the selector .stretch-display is
updated accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/blog/src/app/`(blog)/[slug]/page.tsx:
- Around line 142-162: Wrap the newsletter Input and Subscribe Button in a
proper <form> and wire it to a server-action submit handler so the CTA actually
sends a subscription request: create an async server action (e.g., export async
function subscribe(formData) { "use server"; /* validate email, call API/DB,
handle errors */ }) and set the form's action to that handler; ensure the Input
has a name (e.g., name="email") so formData.get("email") works, the Button
remains type="submit", and add minimal client feedback (success/error) or
redirect handling inside the server action. Use the existing Input and Button
components and update the page component to import/attach the server action to
the form.
- Around line 115-137: The "Share this article" anchors use static profile URLs
from socialIcons so they don't share the current post; update the code that
renders socialIcons (the map over socialIcons in page.tsx) to compute each
anchor href dynamically using the current post's absolute URL and title (derive
the URL from window.location.href or the page's canonical/permalink passed into
the component) and the appropriate sharing endpoint for each platform (e.g.,
Twitter, Facebook, LinkedIn), keep target="_blank" and rel="noopener
noreferrer", and ensure aria-label and the icon (fa-... referencing
socialLink.icon) reflect the post title so the CTA actually shares the current
article rather than linking to static brand pages.
- Around line 57-63: The Avatar is using a hardcoded src ("/avatar.jpg") so the
frontmatter field page.data?.authorSrc is never used; update the Avatar
component (where it renders inside the conditional for page.data?.authorSrc) to
pass page.data.authorSrc as the src prop (with an optional fallback to the
existing "/avatar.jpg" if you want a default) so the authorSrc frontmatter
controls the image.

In `@apps/blog/src/app/`(blog)/page.tsx:
- Line 141: The current call to BlogGrid uses items.slice(1, -1) which removes
both the featured post and the last post; change the slicing to only exclude the
featured item by using items.slice(1) so BlogGrid(items.slice(1), pageSize={12})
(locate the BlogGrid usage in page.tsx and update the slice from slice(1, -1) to
slice(1)).
- Line 62: The mapping sets authorSrc to null which discards the original avatar
source; locate the item mapping (the array .map callback in page.tsx where
authorSrc: null is assigned) and change it to pass through the real value (e.g.,
authorSrc: item.authorSrc or the appropriate source property) or omit the forced
null so downstream avatar logic can receive the actual URL; update the mapped
object returned by that mapping function to include the correct authorSrc symbol
instead of null.
- Around line 100-109: The featured block dereferences items[0] and always
renders an <img>, which will break on empty lists or missing image data; update
the rendering logic in the component that uses items (the anchor block with
href={items[0].url} and the <img> using items[0].imageSrc/items[0].imageAlt):
first check that items && items.length > 0 and only render the anchor/featured
layout when true (otherwise render nothing or a fallback), and within that block
guard the image by rendering the <img> only when items[0].imageSrc exists
(fallback to a placeholder element or an empty alt text when imageSrc is
missing) to avoid dereferencing undefined and broken media.

In `@apps/blog/src/components/BlogGrid.tsx`:
- Around line 79-80: The grid container's className (the div with "grid
sm:grid-cols-[1fr_384px] overflow-hidden border-b pb-4 sm:pb-6
border-stroke-neutral") is missing the "group" utility, so the child element
that uses "group-hover:…" (the image/thumbnail element around lines ~126-127)
will never receive hover state; fix by adding "group" to that parent container's
className so the child "group-hover:scale-105" (or similar) classes can trigger
on hover.
- Around line 106-113: The Avatar instance in BlogGrid is using hardcoded src
and alt values; change the Avatar props to use the post data instead: set src to
post.authorSrc (or post?.authorSrc) and set alt to the author's name (e.g.,
post.author or post?.author.name/contact field) so each card shows the correct
author image and alt text; keep other props like format/size/disabled as-is
unless intentionally different.
- Around line 61-69: formatDate is parsing date-only strings with new Date(iso)
then formatting in the user's local timezone, causing a one-day shift in
negative UTC offsets; update the formatDate function to parse the input as UTC
and format in UTC (e.g., construct the Date using Date.UTC or by appending
"T00:00:00Z" to the ISO date) and include timeZone: "UTC" in the
toLocaleDateString options so dates like "2025-05-26" always render as that UTC
date; change the implementation inside formatDate to perform these steps.

In `@packages/eclipse/src/styles/fonts.css`:
- Around line 28-29: The html selector's font-family currently uses a quoted
name and no generic fallback; update the font-family declaration in the html
rule so the font name is unquoted and add a fallback chain (e.g., Inter,
system-ui, sans-serif) to satisfy font-family-name-quotes and
font-family-no-missing-generic-family-keyword lint rules.

---

Nitpick comments:
In `@apps/blog/src/components/BlogGrid.tsx`:
- Around line 19-20: The prop type for badge on the Post object (badge?: string
| null) is inconsistent with how it's rendered in BlogGrid (post.badge currently
causes the UI to always show the literal "Release"); either change the type to a
boolean (e.g., badge?: boolean) and update rendering logic in the BlogGrid
component to conditionally render the hardcoded "Release" when true, or keep
badge as string and update the rendering to display post.badge value (and handle
null/undefined) where the current "Release" label is output; locate uses of
post.badge in BlogGrid.tsx and adjust the type and render logic to match the
chosen approach.

In `@packages/eclipse/src/styles/fonts.css`:
- Around line 2-13: The `@font-face` declaration for font-family "Mona Sans VF" is
missing a font-display setting which can cause FOIT; add font-display: swap to
each `@font-face` block (both entries for "Mona Sans VF") so browsers show
fallback text immediately and swap in the webfont when ready, keeping the rest
of the descriptors (src, font-weight, font-stretch, font-style,
font-optical-sizing) unchanged.

In `@packages/eclipse/src/styles/globals.css`:
- Around line 743-745: Replace the raw variation setting in the .stretch-display
rule: remove font-variation-settings: "wdth" 125; and use the idiomatic property
font-stretch: 125%; (you can keep a font-variation-settings fallback only if you
explicitly need to target a specific variable font instance, but prefer
font-stretch for the width axis). Ensure the selector .stretch-display is
updated accordingly.

In `@packages/ui/package.json`:
- Line 11: The export map currently exposes "./data/*" -> "./src/data/*.ts",
which expands every future file under src/data and risks accidental public API.
Replace the wildcard export in package.json by either (a) listing explicit
exports for each data module you intend to publish (e.g., "./data/foo":
"./src/data/foo.ts", "./data/bar": "./src/data/bar.ts") or (b) create a single
barrel entry (e.g., "./data": "./src/data/index.ts") that re-exports only the
stable symbols, and remove the "./data/*": "./src/data/*.ts" entry to avoid
implicit semver commitments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a27471a1-1acd-43ff-9c4a-c47d78969711

📥 Commits

Reviewing files that changed from the base of the PR and between ac8da00 and fe8f43c.

⛔ Files ignored due to path filters (3)
  • apps/blog/public/fonts/MonaSansVF[wdth,wght,opsz,ital].woff2 is excluded by !**/*.woff2
  • apps/blog/public/fonts/monaspace_neon_var.woff is excluded by !**/*.woff
  • apps/blog/public/fonts/monaspace_neon_var.woff2 is excluded by !**/*.woff2
📒 Files selected for processing (11)
  • apps/blog/source.config.ts
  • apps/blog/src/app/(blog)/[slug]/page.tsx
  • apps/blog/src/app/(blog)/page.tsx
  • apps/blog/src/app/global.css
  • apps/blog/src/app/layout.tsx
  • apps/blog/src/components/BlogGrid.tsx
  • packages/eclipse/src/components/inline-toc.tsx
  • packages/eclipse/src/styles/fonts.css
  • packages/eclipse/src/styles/globals.css
  • packages/ui/package.json
  • packages/ui/src/data/footer.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (6)
apps/blog/src/app/(blog)/page.tsx (3)

140-140: ⚠️ Potential issue | 🟠 Major

slice(1, -1) drops the final post unintentionally.

If only the featured post should be excluded, this should be slice(1).

Suggested fix
-          <BlogGrid items={items.slice(1, -1)} pageSize={12} />
+          <BlogGrid items={items.slice(1)} pageSize={12} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/page.tsx at line 140, The current use of
items.slice(1, -1) in the BlogGrid rendering (BlogGrid items={items.slice(1,
-1)} pageSize={12}) unintentionally drops the last post; change it to
items.slice(1) so only the featured first item is excluded and all remaining
posts are passed to BlogGrid (update the BlogGrid prop to use items.slice(1)
instead of items.slice(1, -1)).

62-62: ⚠️ Potential issue | 🟠 Major

authorSrc is being dropped during mapping.

Line 62 hardcodes authorSrc: null, so avatar source never reaches the featured card/grid.

Suggested fix
-      authorSrc: null,
+      authorSrc: (data.authorSrc as string | undefined) ?? null,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/page.tsx at line 62, The mapping hardcodes
authorSrc: null in apps/blog/src/app/(blog)/page.tsx so avatar URLs are never
passed to the featured card/grid; change the mapping to pull the real author
image field from the source object (e.g., use the post/article/item.authorSrc or
post.author?.src property used elsewhere) and only fall back to null when that
property is missing, and ensure the resulting value is passed into the featured
card/grid prop that consumes authorSrc.

100-109: ⚠️ Potential issue | 🟠 Major

Guard featured rendering for empty posts and missing featured image.

items[0] is dereferenced unconditionally; empty datasets will crash. The featured image is also rendered without checking source existence.

Also applies to: 125-137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/page.tsx around lines 100 - 109, The featured post
block unconditionally dereferences items[0] and renders an <img> from
items[0].imageSrc, which will crash on empty datasets or missing image; update
the page component rendering logic to first guard on items && items.length > 0
before rendering the featured post markup (the block around items[0] usage) and,
inside that block, only render the <img> when items[0].imageSrc is truthy (or
render a fallback/placeholder) to avoid referencing undefined; apply the same
guards to the duplicate featured block referenced around lines 125-137 so both
occurrences (items[0] usages) are protected.
apps/blog/src/app/(blog)/[slug]/page.tsx (3)

57-63: ⚠️ Potential issue | 🟠 Major

Use frontmatter authorSrc in the Avatar component.

The block is conditioned on page.data?.authorSrc, but Line 60 still hardcodes "/avatar.jpg", so the new field is ignored at runtime.

Suggested fix
                 {page.data?.authorSrc && (
                   <Avatar
                     format="image"
-                    src="/avatar.jpg"
-                    alt="Disabled user"
+                    src={page.data.authorSrc}
+                    alt={page.data.authors[0]}
                     size="lg"
-                    disabled
                   />
                 )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/[slug]/page.tsx around lines 57 - 63, The Avatar
component is conditioned on page.data?.authorSrc but still uses a hardcoded src
("/avatar.jpg"); update the Avatar props in the conditional block (the Avatar
element in page.tsx) to use page.data.authorSrc (or page.data?.authorSrc) as the
src value, optionally keeping a fallback if desired, so the frontmatter
authorSrc is actually used at runtime.

120-137: ⚠️ Potential issue | 🟠 Major

“Share this article” currently links to static social profiles, not share intents.

Line 122 uses socialLink.url, so users are not sharing the current article.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/[slug]/page.tsx around lines 120 - 137, The mapped
links are using socialLink.url (static profiles) instead of share intents for
the current article; update the mapping in the component that renders
socialIcons so href is a generated share URL (e.g.,
buildShareUrl(socialLink.icon, encodeURIComponent(currentUrl),
encodeURIComponent(title))) instead of socialLink.url. Use the page's canonical
URL (compose from site base + slug or use window.location / Next.js
router/usePathname) and encodeURIComponent for title and url, switch key usage
to socialLink.icon or id instead of idx if available, and replace socialLink.url
with the generated shareUrl when rendering the <a> tag.

142-162: ⚠️ Potential issue | 🟠 Major

Newsletter CTA is non-functional without a <form> action/handler.

The input + submit button are rendered without submit wiring, so no subscription request can be sent.

I can draft a minimal server-action form wiring (with email validation and success/error state) if you want me to open a follow-up implementation issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/[slug]/page.tsx around lines 142 - 162, The
newsletter CTA currently renders an Input (id="newsletter") and a submit Button
but has no form or submit handler; wrap the input and Button in a <form> and
wire an onSubmit to a handler (e.g., subscribeNewsletter or
subscribeNewsletterAction) that reads the email value, performs basic email
validation, calls the subscription API/server action, and sets success/error
state for UI feedback; ensure the handler prevents default (for client form) or
use a Next.js server action if preferred, and update the Input/Button usage so
the Button type="submit" triggers the handler and shows the result to the user.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/blog/src/app/`(blog)/[slug]/page.tsx:
- Around line 53-56: The render logic dereferences page.data.authors.length
without ensuring authors exists; update the conditional in the JSX that branches
on page.data.authors to first guard the property (e.g., use page.data.authors &&
page.data.authors.length or page.data.authors?.length consistently) before
accessing .length in the ternary, and handle the missing-authors case (render
nothing or a fallback) in the branch that currently uses
page.data.authors.length === 1 so the component (page.tsx) never throws when
authors is undefined.

In `@apps/blog/src/app/`(blog)/page.tsx:
- Line 59: The assignment that does date: data.date ? new
Date(data.date).toISOString() : "" can throw RangeError for malformed
frontmatter; update the code in page.tsx to validate the parsed date before
calling toISOString by creating a Date object from data.date (referencing
data.date and the date property assignment), then check that
!isNaN(parsedDate.getTime()) (or Date.parse(data.date) is not NaN) and only then
use parsedDate.toISOString(), otherwise set the field to an empty string (or
fallback); alternatively wrap the toISOString call in a try/catch and fallback
to "" on failure.

---

Duplicate comments:
In `@apps/blog/src/app/`(blog)/[slug]/page.tsx:
- Around line 57-63: The Avatar component is conditioned on page.data?.authorSrc
but still uses a hardcoded src ("/avatar.jpg"); update the Avatar props in the
conditional block (the Avatar element in page.tsx) to use page.data.authorSrc
(or page.data?.authorSrc) as the src value, optionally keeping a fallback if
desired, so the frontmatter authorSrc is actually used at runtime.
- Around line 120-137: The mapped links are using socialLink.url (static
profiles) instead of share intents for the current article; update the mapping
in the component that renders socialIcons so href is a generated share URL
(e.g., buildShareUrl(socialLink.icon, encodeURIComponent(currentUrl),
encodeURIComponent(title))) instead of socialLink.url. Use the page's canonical
URL (compose from site base + slug or use window.location / Next.js
router/usePathname) and encodeURIComponent for title and url, switch key usage
to socialLink.icon or id instead of idx if available, and replace socialLink.url
with the generated shareUrl when rendering the <a> tag.
- Around line 142-162: The newsletter CTA currently renders an Input
(id="newsletter") and a submit Button but has no form or submit handler; wrap
the input and Button in a <form> and wire an onSubmit to a handler (e.g.,
subscribeNewsletter or subscribeNewsletterAction) that reads the email value,
performs basic email validation, calls the subscription API/server action, and
sets success/error state for UI feedback; ensure the handler prevents default
(for client form) or use a Next.js server action if preferred, and update the
Input/Button usage so the Button type="submit" triggers the handler and shows
the result to the user.

In `@apps/blog/src/app/`(blog)/page.tsx:
- Line 140: The current use of items.slice(1, -1) in the BlogGrid rendering
(BlogGrid items={items.slice(1, -1)} pageSize={12}) unintentionally drops the
last post; change it to items.slice(1) so only the featured first item is
excluded and all remaining posts are passed to BlogGrid (update the BlogGrid
prop to use items.slice(1) instead of items.slice(1, -1)).
- Line 62: The mapping hardcodes authorSrc: null in
apps/blog/src/app/(blog)/page.tsx so avatar URLs are never passed to the
featured card/grid; change the mapping to pull the real author image field from
the source object (e.g., use the post/article/item.authorSrc or post.author?.src
property used elsewhere) and only fall back to null when that property is
missing, and ensure the resulting value is passed into the featured card/grid
prop that consumes authorSrc.
- Around line 100-109: The featured post block unconditionally dereferences
items[0] and renders an <img> from items[0].imageSrc, which will crash on empty
datasets or missing image; update the page component rendering logic to first
guard on items && items.length > 0 before rendering the featured post markup
(the block around items[0] usage) and, inside that block, only render the <img>
when items[0].imageSrc is truthy (or render a fallback/placeholder) to avoid
referencing undefined; apply the same guards to the duplicate featured block
referenced around lines 125-137 so both occurrences (items[0] usages) are
protected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d04f7c0e-9a6b-47ca-9264-f1e5d2ece99d

📥 Commits

Reviewing files that changed from the base of the PR and between fe8f43c and 142b21d.

📒 Files selected for processing (4)
  • apps/blog/src/app/(blog)/[slug]/page.tsx
  • apps/blog/src/app/(blog)/page.tsx
  • packages/ui/package.json
  • packages/ui/src/data/footer.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/data/footer.ts
  • packages/ui/package.json

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (8)
apps/blog/src/components/BlogGrid.tsx (2)

230-237: ⚠️ Potential issue | 🟠 Major

Avatar still uses hardcoded author data.

post.authorSrc is checked, but the actual Avatar props still ignore post-specific values.

Suggested fix
                   {post?.authorSrc && (
                     <Avatar
                       format="image"
-                      src="/avatar.jpg"
-                      alt="Disabled user"
+                      src={post.authorSrc}
+                      alt={post.author ?? "Author avatar"}
                       size="lg"
-                      disabled
                     />
                   )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/components/BlogGrid.tsx` around lines 230 - 237, The Avatar
instance is using hardcoded props despite checking post?.authorSrc; update the
Avatar props in BlogGrid (the Avatar JSX where post?.authorSrc is checked) to
use the post data: set src={post.authorSrc} (or a fallback), set alt to the
author's name (e.g., alt={post.authorName || 'Author avatar'}), and remove or
conditionally set disabled/size as needed based on post metadata; ensure you
keep the conditional wrapper (post?.authorSrc) so Avatar only renders when a
source exists.

203-203: ⚠️ Potential issue | 🟡 Minor

group-hover animation won’t fire without a group parent.

The image uses group-hover:*, but the parent link class does not include group.

Suggested fix
-            className="grid sm:grid-cols-[1fr_384px] overflow-hidden border-b pb-4 sm:pb-6 border-stroke-neutral gap-8"
+            className="group grid sm:grid-cols-[1fr_384px] overflow-hidden border-b pb-4 sm:pb-6 border-stroke-neutral gap-8"

Also applies to: 250-250

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/components/BlogGrid.tsx` at line 203, The image's group-hover
utilities won't run because the parent link element is missing the "group"
class; in BlogGrid (the link/anchor/NextLink component that wraps each post
image) add "group" to its className so the child image's group-hover:* rules
activate, and apply the same change for the second instance where an image uses
group-hover (both occurrences found in BlogGrid.tsx).
apps/blog/src/app/(blog)/[slug]/page.tsx (3)

57-64: ⚠️ Potential issue | 🟠 Major

Avatar still ignores frontmatter authorSrc.

The condition checks page.data?.authorSrc, but the rendered Avatar still uses hardcoded values.

Suggested fix
                 {page.data?.authorSrc && (
                   <Avatar
                     format="image"
-                    src="/avatar.jpg"
-                    alt="Disabled user"
+                    src={page.data.authorSrc}
+                    alt={page.data.authors[0] ?? "Author avatar"}
                     size="lg"
-                    disabled
                   />
                 )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/[slug]/page.tsx around lines 57 - 64, The Avatar
rendering is ignoring frontmatter because it uses hardcoded props; update the
Avatar component (the JSX using Avatar) to pass the frontmatter values: set src
to page.data?.authorSrc, set alt to something like page.data?.author or a
sensible fallback (e.g., "Author avatar"), and keep existing props (format,
size, etc.); ensure the conditional stays page.data?.authorSrc so Avatar
displays the provided image and falls back correctly when absent.

115-137: ⚠️ Potential issue | 🟠 Major

“Share this article” currently opens brand profiles, not share intents.

The CTA copy and behavior are mismatched: these links don’t share the current post URL/title.

Suggested direction
+          {/*
+            Build share URLs from the current article instead of footer profile links.
+            Example platforms: X, LinkedIn, Facebook.
+          */}
+          {(() => {
+            const postUrl = encodeURIComponent(`https://www.prisma.io/blog/${params.slug}`);
+            const postTitle = encodeURIComponent(page.data.title);
+            const shareLinks = [
+              {
+                title: "X",
+                icon: "x-twitter",
+                url: `https://twitter.com/intent/tweet?url=${postUrl}&text=${postTitle}`,
+              },
+              {
+                title: "LinkedIn",
+                icon: "linkedin",
+                url: `https://www.linkedin.com/sharing/share-offsite/?url=${postUrl}`,
+              },
+            ];
+            return shareLinks.map((socialLink, idx) => (
+              <a
+                key={idx}
+                href={socialLink.url}
+                target="_blank"
+                rel="noopener noreferrer"
+                aria-label={`Share ${page.data.title} on ${socialLink.title}`}
+              >
+                ...
+              </a>
+            ));
+          })()}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/[slug]/page.tsx around lines 115 - 137, The links
in the socialIcons render use socialLink.url (opening brand profiles) instead of
invoking a share intent; update the component so the anchor hrefs are
constructed share URLs (e.g., for Twitter, Facebook, LinkedIn, WhatsApp) that
include the current post URL and title via encodeURIComponent rather than using
socialLink.url. Either replace each socialLink.url with a computed shareUrl when
mapping (buildShareUrl(postUrl, post.title, socialLink.platform) or inline the
platform-specific templates) and keep the rest of the anchor props (target, rel,
aria-label, key) and icon rendering unchanged so the CTA actually shares the
current article.

142-162: ⚠️ Potential issue | 🟠 Major

Newsletter CTA is still non-functional.

The input/button UI is rendered, but there is no <form> action or submit handler to process subscriptions.

Suggested fix pattern
-          <div className="input flex items-center gap-2 justify-center">
+          <form action={subscribe} className="input flex items-center gap-2 justify-center">
             <Input
               type="email"
               id="newsletter"
+              name="email"
               size="2xl"
               placeholder="email@example.com"
               className="w-62"
             />
             <Button
               size="xl"
               variant="ppg"
               type="submit"
               className="whitespace-nowrap font-semibold"
             >
               Subscribe
             </Button>
-          </div>
+          </form>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/[slug]/page.tsx around lines 142 - 162, The
newsletter CTA renders Input and Button but lacks a form and submit handling;
wrap the Input and Button in a <form> and add a controlled state (e.g.,
newsletterEmail) plus an onSubmit handler (e.g., handleSubscribe or
onSubmitSubscribe) that prevents default, validates the email, calls your
newsletter subscribe API endpoint, and updates UI state (loading, success,
error) or shows a toast; ensure the Button uses type="submit" and the Input
value/onChange are wired to newsletterEmail so submissions actually send the
captured email to the backend.
apps/blog/src/app/(blog)/page.tsx (3)

101-107: ⚠️ Potential issue | 🟠 Major

Featured rendering should guard empty lists before items[0] access.

Directly dereferencing items[0] will throw if the collection is empty.

Suggested fix pattern
+  if (items.length === 0) {
+    return (
+      <main className="flex-1 w-full max-w-249 mx-auto px-4 py-8">
+        <h1 className="stretch-display text-4xl font-bold mb-2 landing-h1 text-center mt-9 font-display">
+          Blog
+        </h1>
+      </main>
+    );
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/page.tsx around lines 101 - 107, The featured-item
JSX dereferences items[0] without checking length, which will throw if items is
empty; update the component rendering logic (the block using items[0], e.g., the
<a href={items[0].url} ...> and nested <img src={items[0].imageSrc} ...>) to
first guard against an empty array — either return null/placeholder when
items?.length === 0 or conditionally render the featured markup only when
items?.length > 0, ensuring you reference the same "items" variable used in the
component.

141-141: ⚠️ Potential issue | 🟠 Major

slice(1, -1) still drops the last post.

If the intent is to exclude only the featured item, this should be slice(1).

Suggested fix
-          <BlogGrid items={items.slice(1, -1)} pageSize={12} />
+          <BlogGrid items={items.slice(1)} pageSize={12} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/page.tsx at line 141, The current use of
items.slice(1, -1) in the BlogGrid rendering drops the last post
unintentionally; update the call in the BlogGrid JSX (where BlogGrid is invoked
with items.slice(1, -1) and pageSize={12}) to use items.slice(1) so only the
first (featured) item is excluded and the last post is preserved.

63-63: ⚠️ Potential issue | 🟠 Major

authorSrc is still discarded during mapping.

Forcing authorSrc to null prevents avatar rendering downstream even when frontmatter provides it.

Suggested fix
-      authorSrc: null,
+      authorSrc: (data.authorSrc as string | undefined) ?? null,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/page.tsx at line 63, The mapping unconditionally
sets authorSrc to null which discards any frontmatter-provided avatar; update
the mapping logic (where authorSrc is assigned) to use the source from the post
frontmatter (e.g., post.frontmatter.authorSrc or frontmatter.authorSrc) and only
fallback to null or undefined when that value is absent so downstream avatar
rendering can receive the real URL (modify the code that currently sets
authorSrc: null to assign the frontmatter value instead).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/blog/src/components/BlogGrid.tsx`:
- Around line 35-162: PaginationWithEllipsis renders and wires links for pages
outside [1, totalPages] when totalPages is small; update the component to
validate and clamp page targets and skip rendering invalid links: ensure you
early-return or render minimal controls when totalPages <= 1, only render page
"2" / "totalPages - 1" / neighboring numbers if those values are >=1 and <=
totalPages, and wrap all setCurrentPage calls with a clamp (e.g., Math.max(1,
Math.min(target, totalPages))) or conditional guards so clicks cannot set
out-of-range pages; update the handlers and conditional branches in
PaginationWithEllipsis to use these checks for PaginationLink,
PaginationPrevious, PaginationNext, and ellipsis logic.

---

Duplicate comments:
In `@apps/blog/src/app/`(blog)/[slug]/page.tsx:
- Around line 57-64: The Avatar rendering is ignoring frontmatter because it
uses hardcoded props; update the Avatar component (the JSX using Avatar) to pass
the frontmatter values: set src to page.data?.authorSrc, set alt to something
like page.data?.author or a sensible fallback (e.g., "Author avatar"), and keep
existing props (format, size, etc.); ensure the conditional stays
page.data?.authorSrc so Avatar displays the provided image and falls back
correctly when absent.
- Around line 115-137: The links in the socialIcons render use socialLink.url
(opening brand profiles) instead of invoking a share intent; update the
component so the anchor hrefs are constructed share URLs (e.g., for Twitter,
Facebook, LinkedIn, WhatsApp) that include the current post URL and title via
encodeURIComponent rather than using socialLink.url. Either replace each
socialLink.url with a computed shareUrl when mapping (buildShareUrl(postUrl,
post.title, socialLink.platform) or inline the platform-specific templates) and
keep the rest of the anchor props (target, rel, aria-label, key) and icon
rendering unchanged so the CTA actually shares the current article.
- Around line 142-162: The newsletter CTA renders Input and Button but lacks a
form and submit handling; wrap the Input and Button in a <form> and add a
controlled state (e.g., newsletterEmail) plus an onSubmit handler (e.g.,
handleSubscribe or onSubmitSubscribe) that prevents default, validates the
email, calls your newsletter subscribe API endpoint, and updates UI state
(loading, success, error) or shows a toast; ensure the Button uses type="submit"
and the Input value/onChange are wired to newsletterEmail so submissions
actually send the captured email to the backend.

In `@apps/blog/src/app/`(blog)/page.tsx:
- Around line 101-107: The featured-item JSX dereferences items[0] without
checking length, which will throw if items is empty; update the component
rendering logic (the block using items[0], e.g., the <a href={items[0].url} ...>
and nested <img src={items[0].imageSrc} ...>) to first guard against an empty
array — either return null/placeholder when items?.length === 0 or conditionally
render the featured markup only when items?.length > 0, ensuring you reference
the same "items" variable used in the component.
- Line 141: The current use of items.slice(1, -1) in the BlogGrid rendering
drops the last post unintentionally; update the call in the BlogGrid JSX (where
BlogGrid is invoked with items.slice(1, -1) and pageSize={12}) to use
items.slice(1) so only the first (featured) item is excluded and the last post
is preserved.
- Line 63: The mapping unconditionally sets authorSrc to null which discards any
frontmatter-provided avatar; update the mapping logic (where authorSrc is
assigned) to use the source from the post frontmatter (e.g.,
post.frontmatter.authorSrc or frontmatter.authorSrc) and only fallback to null
or undefined when that value is absent so downstream avatar rendering can
receive the real URL (modify the code that currently sets authorSrc: null to
assign the frontmatter value instead).

In `@apps/blog/src/components/BlogGrid.tsx`:
- Around line 230-237: The Avatar instance is using hardcoded props despite
checking post?.authorSrc; update the Avatar props in BlogGrid (the Avatar JSX
where post?.authorSrc is checked) to use the post data: set src={post.authorSrc}
(or a fallback), set alt to the author's name (e.g., alt={post.authorName ||
'Author avatar'}), and remove or conditionally set disabled/size as needed based
on post metadata; ensure you keep the conditional wrapper (post?.authorSrc) so
Avatar only renders when a source exists.
- Line 203: The image's group-hover utilities won't run because the parent link
element is missing the "group" class; in BlogGrid (the link/anchor/NextLink
component that wraps each post image) add "group" to its className so the child
image's group-hover:* rules activate, and apply the same change for the second
instance where an image uses group-hover (both occurrences found in
BlogGrid.tsx).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7f26c700-90ce-4a64-b7fa-cdd298c1a826

📥 Commits

Reviewing files that changed from the base of the PR and between 142b21d and 3f34703.

📒 Files selected for processing (7)
  • apps/blog/source.config.ts
  • apps/blog/src/app/(blog)/[slug]/page.tsx
  • apps/blog/src/app/(blog)/page.tsx
  • apps/blog/src/components/BlogGrid.tsx
  • packages/eclipse/src/components/pagination.tsx
  • packages/ui/package.json
  • packages/ui/src/data/footer.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui/package.json

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (11)
apps/blog/src/app/(blog)/[slug]/page.tsx (4)

57-63: ⚠️ Potential issue | 🟠 Major

Use page.data.authorSrc in Avatar instead of a hardcoded path.

Line 60 still ignores frontmatter-driven avatars, so author attribution is incorrect at runtime.

Suggested fix
<Avatar
  format="image"
- src="/avatar.jpg"
- alt="Disabled user"
+ src={page.data.authorSrc}
+ alt={page.data.authors?.[0] ?? "Author avatar"}
  size="lg"
- disabled
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/[slug]/page.tsx around lines 57 - 63, The Avatar
currently uses a hardcoded src ("/avatar.jpg") instead of the frontmatter-driven
value; update the Avatar props in the component that renders
page.data?.authorSrc (the Avatar JSX where page.data?.authorSrc is checked) to
pass page.data.authorSrc as the src prop (e.g., src={page.data.authorSrc}) so
the author avatar is sourced from the page data rather than a static path.

146-162: ⚠️ Potential issue | 🟠 Major

Newsletter CTA is still non-functional without form submission wiring.

This block renders submit UI but has no <form action> or handler, so subscriptions cannot be sent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/[slug]/page.tsx around lines 146 - 162, The
newsletter UI renders an Input and Button but lacks a form or submit handler, so
subscriptions never get sent; wrap the Input and Button in a <form> and wire it
to a handler (either a client-side onSubmit function in the page component or a
Next.js server/action or API route) that reads the email from the Input
(identify the Input by id="newsletter") and posts it to your subscription
endpoint (or calls a subscribe function), handle validation/errors, and
preventDefault when using client-side submission; update the page component
(where Input and Button are rendered) to call that handler on submit and ensure
Button stays type="submit" so the form actually triggers the handler.

53-56: ⚠️ Potential issue | 🟠 Major

Guard authors before dereferencing .length.

Line 55 can still throw when authors is missing because the branch uses page.data.authors.length without optional chaining.

Suggested fix
- {page.data.authors?.length > 1 ? (
-   page.data.authors.join(", ")
- ) : page.data.authors.length === 1 ? (
+ {page.data.authors?.length > 1 ? (
+   page.data.authors.join(", ")
+ ) : page.data.authors?.length === 1 ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/[slug]/page.tsx around lines 53 - 56, The rendering
ternary accesses page.data.authors.length without first ensuring authors exists;
update the conditional in the JSX that renders authors (the ternary using
page.data.authors) to guard against undefined by using optional chaining or a
default value (e.g., treat page.data.authors ?? [] or use
page.data.authors?.length) so you never dereference .length on undefined and the
fallback branch still handles zero/missing authors correctly.

120-137: ⚠️ Potential issue | 🟠 Major

“Share this article” still links to static profile URLs.

Line 122 uses socialLink.url, so this CTA doesn’t share the current post URL/title.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/[slug]/page.tsx around lines 120 - 137, The CTA is
using socialLink.url (static profile URLs) so the "Share this article" buttons
don't share the current post; update the mapping in the JSX that renders
socialIcons (inside the map over socialIcons) to compute a shareHref for
share-type icons by building a share URL that includes the current post's URL
and title (e.g., use the page/post canonical URL or construct from the post slug
and metadata.title, and encodeURIComponent) and use that shareHref as the anchor
href instead of socialLink.url; keep using socialLink.url for non-share/profile
icons and preserve target="_blank" and rel attributes.
apps/blog/src/app/(blog)/page.tsx (3)

63-63: ⚠️ Potential issue | 🟠 Major

Don’t discard authorSrc in mapping.

Line 63 hardcodes authorSrc to null, so downstream avatar rendering never receives real data.

Suggested fix
- authorSrc: null,
+ authorSrc: (data.authorSrc as string | undefined) ?? null,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/page.tsx at line 63, The mapping in page.tsx
currently hardcodes authorSrc: null which drops real avatar data; update the
object mapping that sets authorSrc (the line with "authorSrc: null") to use the
source author's field (e.g., author.authorSrc or author.src depending on the
source model) and/or fall back to undefined/default if missing, so downstream
avatar rendering receives the actual URL; ensure you update the mapper used when
building author props (the map callback where authorSrc is set) and keep any
existing null/undefined-safe checks.

101-101: ⚠️ Potential issue | 🟠 Major

slice(1, -1) unintentionally drops the last post.

If only the featured post should be excluded from the grid, this should be slice(1).

Suggested fix
- <BlogGrid items={items.slice(1, -1)} pageSize={12} />
+ <BlogGrid items={items.slice(1)} pageSize={12} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/page.tsx at line 101, The BlogGrid is currently
passed items.slice(1, -1) which unintentionally omits the last post; update the
call that renders the grid (BlogGrid) to use items.slice(1) so only the featured
first item is excluded and the rest (including the last post) are included.
Locate the BlogGrid usage in page.tsx and replace the slice bounds accordingly.

59-59: ⚠️ Potential issue | 🟠 Major

Validate parsed date before calling toISOString().

Line 59 can throw on malformed frontmatter dates and break render.

Suggested fix
- date: data.date ? new Date(data.date).toISOString() : "",
+ date: (() => {
+   if (!data.date) return "";
+   const parsed = new Date(data.date);
+   return Number.isNaN(parsed.getTime()) ? "" : parsed.toISOString();
+ })(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/app/`(blog)/page.tsx at line 59, The current assignment "date:
data.date ? new Date(data.date).toISOString() : ''" can throw for malformed
frontmatter; update the logic in apps/blog/src/app/(blog)/page.tsx to parse the
date into a Date object first (e.g., const parsed = new Date(data.date)),
validate it with isNaN(parsed.getTime()) (or parsed.toString() === 'Invalid
Date'), and only call toISOString() when valid, otherwise fall back to an empty
string or a safe default; locate and fix the expression that constructs the date
property so it guards against invalid dates.
apps/blog/src/components/BlogGrid.tsx (4)

203-210: ⚠️ Potential issue | 🟡 Minor

group-hover effects won’t trigger without a group parent.

Hover-scale classes are present on children, but parent wrappers don’t include group, so the effect never activates.

Suggested fix
- className="grid grid-cols-1 sm:grid-cols-2 rounded-square overflow-hidden border border-stroke-neutral shadow-box-low"
+ className="group grid grid-cols-1 sm:grid-cols-2 rounded-square overflow-hidden border border-stroke-neutral shadow-box-low"

- className="grid sm:grid-cols-[1fr_384px] overflow-hidden border-b pb-4 sm:pb-6 border-stroke-neutral gap-8"
+ className="group grid sm:grid-cols-[1fr_384px] overflow-hidden border-b pb-4 sm:pb-6 border-stroke-neutral gap-8"

Also applies to: 247-295

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/components/BlogGrid.tsx` around lines 203 - 210, The hover
scale on child elements (using group-hover) won't activate because the parent
wrapper for the grid in BlogGrid (the element with className="grid grid-cols-1
sm:grid-cols-2 ...") is missing the required "group" utility; add the "group"
class to that parent wrapper (and to the other parent wrappers around the child
elements referenced in the later block where group-hover is used, e.g., the
sibling block around items[1] between lines ~247-295) so child classes like
"group-hover:scale-[1.02]" work as intended.

187-195: ⚠️ Potential issue | 🟡 Minor

Date formatting can drift by one day for date-only strings.

new Date("YYYY-MM-DD") with local toLocaleDateString can render the previous day in negative UTC offsets.

Suggested fix
return date.toLocaleDateString("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
+ timeZone: "UTC",
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/components/BlogGrid.tsx` around lines 187 - 195, The formatDate
function can produce dates off-by-one for date-only ISO strings due to local
timezone parsing; update formatDate to detect "YYYY-MM-DD" inputs and parse them
as UTC (e.g. construct a Date using Date.UTC or append a "T00:00:00Z" before new
Date) so the produced Date represents the intended calendar day, then continue
to call toLocaleDateString("en-US", ...) for formatting; reference the
formatDate helper to implement the UTC-safe parse branch.

33-160: ⚠️ Potential issue | 🟠 Major

Clamp/guard pagination targets for small page counts.

This control can still produce invalid targets (e.g., page 0 or 2 when totalPages is 1) and should not render full controls when pagination is unnecessary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/components/BlogGrid.tsx` around lines 33 - 160,
PaginationWithEllipsis can generate invalid page targets (e.g., 0 or
>totalPages) and renders full controls when totalPages <= 1; guard and clamp
targets and hide controls when unnecessary. In the PaginationWithEllipsis
component, add an early return to render nothing (or a minimal non-interactive
element) when totalPages <= 1; sanitize clicks by clamping any setCurrentPage
calls to Math.max(1, Math.min(target, totalPages)) and replace direct numeric
targets (like 0, 2, totalPages-1) with computed safe values; update handlers
inside PaginationPrevious, PaginationNext and all PaginationLink onClick
callbacks to compute a safeTarget before calling setCurrentPage and use
currentPage/currentPage +/-1 only after validating against totalPages.

274-281: ⚠️ Potential issue | 🟠 Major

Avatar still uses hardcoded values instead of post data.

Lines 277-280 override post.authorSrc/post.author, so cards display incorrect author visuals.

Suggested fix
{post?.authorSrc && (
  <Avatar
    format="image"
-   src="/avatar.jpg"
-   alt="Disabled user"
+   src={post.authorSrc}
+   alt={post.author ?? "Author avatar"}
    size="lg"
-   disabled
  />
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blog/src/components/BlogGrid.tsx` around lines 274 - 281, The Avatar
component is using hardcoded props instead of the post data, causing wrong
author visuals; update the Avatar usage in BlogGrid to use the post object
(e.g., change src="/avatar.jpg" to src={post.authorSrc} and alt="Disabled user"
to alt={post.author}) and stop overriding the post values (remove any
hardcoded/disabled flags that mask real data) so Avatar renders the actual
author image/name from post.authorSrc/post.author.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/blog/src/components/BlogGrid.tsx`:
- Around line 200-243: The featured block unconditionally dereferences items[0]
and duplicates that post because visibleItems still contains it; update the
BlogGrid rendering to first guard the featured section with if (items &&
items.length > 0 && currentPage === 1) before using items[0], and ensure
visibleItems does not include the featured item on page 1 by filtering it out
(compare a unique identifier like url or id) or by computing visibleItems as
items.slice(1) when currentPage === 1; adjust references to items, visibleItems,
and currentPage accordingly so the featured card is only shown when items exists
and is not duplicated in the list.

---

Duplicate comments:
In `@apps/blog/src/app/`(blog)/[slug]/page.tsx:
- Around line 57-63: The Avatar currently uses a hardcoded src ("/avatar.jpg")
instead of the frontmatter-driven value; update the Avatar props in the
component that renders page.data?.authorSrc (the Avatar JSX where
page.data?.authorSrc is checked) to pass page.data.authorSrc as the src prop
(e.g., src={page.data.authorSrc}) so the author avatar is sourced from the page
data rather than a static path.
- Around line 146-162: The newsletter UI renders an Input and Button but lacks a
form or submit handler, so subscriptions never get sent; wrap the Input and
Button in a <form> and wire it to a handler (either a client-side onSubmit
function in the page component or a Next.js server/action or API route) that
reads the email from the Input (identify the Input by id="newsletter") and posts
it to your subscription endpoint (or calls a subscribe function), handle
validation/errors, and preventDefault when using client-side submission; update
the page component (where Input and Button are rendered) to call that handler on
submit and ensure Button stays type="submit" so the form actually triggers the
handler.
- Around line 53-56: The rendering ternary accesses page.data.authors.length
without first ensuring authors exists; update the conditional in the JSX that
renders authors (the ternary using page.data.authors) to guard against undefined
by using optional chaining or a default value (e.g., treat page.data.authors ??
[] or use page.data.authors?.length) so you never dereference .length on
undefined and the fallback branch still handles zero/missing authors correctly.
- Around line 120-137: The CTA is using socialLink.url (static profile URLs) so
the "Share this article" buttons don't share the current post; update the
mapping in the JSX that renders socialIcons (inside the map over socialIcons) to
compute a shareHref for share-type icons by building a share URL that includes
the current post's URL and title (e.g., use the page/post canonical URL or
construct from the post slug and metadata.title, and encodeURIComponent) and use
that shareHref as the anchor href instead of socialLink.url; keep using
socialLink.url for non-share/profile icons and preserve target="_blank" and rel
attributes.

In `@apps/blog/src/app/`(blog)/page.tsx:
- Line 63: The mapping in page.tsx currently hardcodes authorSrc: null which
drops real avatar data; update the object mapping that sets authorSrc (the line
with "authorSrc: null") to use the source author's field (e.g., author.authorSrc
or author.src depending on the source model) and/or fall back to
undefined/default if missing, so downstream avatar rendering receives the actual
URL; ensure you update the mapper used when building author props (the map
callback where authorSrc is set) and keep any existing null/undefined-safe
checks.
- Line 101: The BlogGrid is currently passed items.slice(1, -1) which
unintentionally omits the last post; update the call that renders the grid
(BlogGrid) to use items.slice(1) so only the featured first item is excluded and
the rest (including the last post) are included. Locate the BlogGrid usage in
page.tsx and replace the slice bounds accordingly.
- Line 59: The current assignment "date: data.date ? new
Date(data.date).toISOString() : ''" can throw for malformed frontmatter; update
the logic in apps/blog/src/app/(blog)/page.tsx to parse the date into a Date
object first (e.g., const parsed = new Date(data.date)), validate it with
isNaN(parsed.getTime()) (or parsed.toString() === 'Invalid Date'), and only call
toISOString() when valid, otherwise fall back to an empty string or a safe
default; locate and fix the expression that constructs the date property so it
guards against invalid dates.

In `@apps/blog/src/components/BlogGrid.tsx`:
- Around line 203-210: The hover scale on child elements (using group-hover)
won't activate because the parent wrapper for the grid in BlogGrid (the element
with className="grid grid-cols-1 sm:grid-cols-2 ...") is missing the required
"group" utility; add the "group" class to that parent wrapper (and to the other
parent wrappers around the child elements referenced in the later block where
group-hover is used, e.g., the sibling block around items[1] between lines
~247-295) so child classes like "group-hover:scale-[1.02]" work as intended.
- Around line 187-195: The formatDate function can produce dates off-by-one for
date-only ISO strings due to local timezone parsing; update formatDate to detect
"YYYY-MM-DD" inputs and parse them as UTC (e.g. construct a Date using Date.UTC
or append a "T00:00:00Z" before new Date) so the produced Date represents the
intended calendar day, then continue to call toLocaleDateString("en-US", ...)
for formatting; reference the formatDate helper to implement the UTC-safe parse
branch.
- Around line 33-160: PaginationWithEllipsis can generate invalid page targets
(e.g., 0 or >totalPages) and renders full controls when totalPages <= 1; guard
and clamp targets and hide controls when unnecessary. In the
PaginationWithEllipsis component, add an early return to render nothing (or a
minimal non-interactive element) when totalPages <= 1; sanitize clicks by
clamping any setCurrentPage calls to Math.max(1, Math.min(target, totalPages))
and replace direct numeric targets (like 0, 2, totalPages-1) with computed safe
values; update handlers inside PaginationPrevious, PaginationNext and all
PaginationLink onClick callbacks to compute a safeTarget before calling
setCurrentPage and use currentPage/currentPage +/-1 only after validating
against totalPages.
- Around line 274-281: The Avatar component is using hardcoded props instead of
the post data, causing wrong author visuals; update the Avatar usage in BlogGrid
to use the post object (e.g., change src="/avatar.jpg" to src={post.authorSrc}
and alt="Disabled user" to alt={post.author}) and stop overriding the post
values (remove any hardcoded/disabled flags that mask real data) so Avatar
renders the actual author image/name from post.authorSrc/post.author.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f6d21350-ffb9-4984-836c-66afed83bf1c

📥 Commits

Reviewing files that changed from the base of the PR and between 3f34703 and a3caf28.

📒 Files selected for processing (5)
  • apps/blog/src/app/(blog)/[slug]/page.tsx
  • apps/blog/src/app/(blog)/page.tsx
  • apps/blog/src/components/BlogGrid.tsx
  • packages/ui/package.json
  • packages/ui/src/data/footer.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/data/footer.ts
  • packages/ui/package.json

aidankmcalister
aidankmcalister previously approved these changes Mar 4, 2026
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.

2 participants