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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions apps/website/src/components/landing/RecentArticles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { Section } from '../ui/Section';
import { Container } from '../ui/Container';
import { Eyebrow } from '../ui/Eyebrow';
import { PostCard } from '../blog/PostCard';
import { getRecentNonFeatured } from '../../lib/blog';
import { getRecentPosts } from '../../lib/blog';

/**
* Marketing-home strip showing the three most recent non-featured posts.
* Renders nothing when no eligible posts exist, so the home page stays clean
* while the blog catalog is small.
* Marketing-home strip showing the most recent posts (including any flagged
* `featured`). Renders nothing when no posts exist, so the home page stays
* clean while the blog catalog is small.
*/
export function RecentArticles() {
const posts = getRecentNonFeatured(3);
const posts = getRecentPosts(3);
if (posts.length === 0) return null;

return (
Expand Down
28 changes: 13 additions & 15 deletions apps/website/src/lib/blog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,36 +146,34 @@ describe('blog.ts', () => {
expect(getAllPosts().map((p) => p.slug)).toEqual(['first-post']);
});

it('getRecentNonFeatured excludes the featured post', async () => {
it('getRecentPosts returns newest-first, including featured', async () => {
setupFs({
'2026-05-01-first-post.mdx': post1,
'2026-05-10-second-post.mdx': post2,
});
const { getRecentNonFeatured } = await import('./blog');
// post2 is featured (see fixture at top of file). Expect only post1.
expect(getRecentNonFeatured().map((p) => p.slug)).toEqual(['first-post']);
const { getRecentPosts } = await import('./blog');
// post2 is featured; it should still appear (sorted first by date).
expect(getRecentPosts().map((p) => p.slug)).toEqual(['second-post', 'first-post']);
});

it('getRecentNonFeatured caps at the limit', async () => {
it('getRecentPosts caps at the limit', async () => {
setupFs({
'2026-05-01-first-post.mdx': post1,
'2026-05-05-extra-a.mdx': post1.replace('First Post', 'Extra A'),
'2026-05-06-extra-b.mdx': post1.replace('First Post', 'Extra B'),
'2026-05-07-extra-c.mdx': post1.replace('First Post', 'Extra C'),
'2026-05-10-second-post.mdx': post2,
});
const { getRecentNonFeatured } = await import('./blog');
// post2 is featured and filtered out; remaining 4 should be capped to 3.
const result = getRecentNonFeatured(3);
const { getRecentPosts } = await import('./blog');
const result = getRecentPosts(3);
expect(result).toHaveLength(3);
expect(result.map((p) => p.slug)).not.toContain('second-post');
// Should be the three newest by date.
expect(result.map((p) => p.slug)).toEqual(['second-post', 'extra-c', 'extra-b']);
});

it('getRecentNonFeatured returns [] when only the featured post exists', async () => {
setupFs({
'2026-05-10-second-post.mdx': post2,
});
const { getRecentNonFeatured } = await import('./blog');
expect(getRecentNonFeatured()).toEqual([]);
it('getRecentPosts returns [] when no posts exist', async () => {
setupFs({});
const { getRecentPosts } = await import('./blog');
expect(getRecentPosts()).toEqual([]);
});
});
15 changes: 5 additions & 10 deletions apps/website/src/lib/blog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,13 @@ export function readingTimeMin(markdown: string): number {
}

/**
* Recent posts excluding the one currently surfaced as featured on /blog.
* Most recent posts, including any flagged `featured`.
*
* Used by the home page "Recent articles" section so visitors don't see the
* same headline post twice (once in the featured slot at /blog and again on
* the home page). `getAllPosts()` already sorts newest-first and excludes
* drafts, so this is a thin filter on top.
* Used by the home page "Recent articles" section. `getAllPosts()` already
* sorts newest-first and excludes drafts, so this is just a slice on top.
*
* @param limit Maximum number of posts to return. Defaults to 3.
*/
export function getRecentNonFeatured(limit = 3): Post[] {
const featured = getFeaturedPost();
return getAllPosts()
.filter((p) => p.slug !== featured?.slug)
.slice(0, limit);
export function getRecentPosts(limit = 3): Post[] {
return getAllPosts().slice(0, limit);
}
Loading