at main
6 folders
23 files
port pure lib primitives + vitest coverage
- category-utils: getCategorySlug + makeCategoryTitleResolver (resolver
is config-bound, was a module-level map keyed off the consumer's
categories source)
- post-filtering-utils: calculateMatchScore, filterPostsByCategories
(OR-logic), sortPostsWithMatchScore (match-score tiebreaker only
when >1 selected), getAllCategoriesFromPosts
- client-post-filters: paginatePosts + filterSortAndPaginatePosts
- bluesky: URL parsing, handle resolution, post fetch (unchanged
surface from website)
- markdown-cache: loadMarkdownContent({contentDirPath}) — was a
baked-in path in the website
- 33 tests across the pure-function set (slug/title resolver edge,
match scoring, OR-filter, sort tiebreaker, pagination boundaries,
URL regex acceptance/rejection); skipping component renders and
fetch-glue per testing-philosophy.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
port pure lib primitives + vitest coverage
- category-utils: getCategorySlug + makeCategoryTitleResolver (resolver
is config-bound, was a module-level map keyed off the consumer's
categories source)
- post-filtering-utils: calculateMatchScore, filterPostsByCategories
(OR-logic), sortPostsWithMatchScore (match-score tiebreaker only
when >1 selected), getAllCategoriesFromPosts
- client-post-filters: paginatePosts + filterSortAndPaginatePosts
- bluesky: URL parsing, handle resolution, post fetch (unchanged
surface from website)
- markdown-cache: loadMarkdownContent({contentDirPath}) — was a
baked-in path in the website
- 33 tests across the pure-function set (slug/title resolver edge,
match scoring, OR-filter, sort tiebreaker, pagination boundaries,
URL regex acceptance/rejection); skipping component renders and
fetch-glue per testing-philosophy.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add slot-aware BlogPostLayout with default region components
Extracts BlogPostMeta, BlogPostCover, BlogPostBody as standalone exports so
consumers can wrap or restyle individual regions without forking the layout.
BlogPostLayout now accepts ReactNode slot props for meta, cover, title, body,
sidebar, and comments — undefined uses the default, null omits the region, a
ReactNode overrides it. FeaturedSidebar is the default sidebar when config is
supplied; CollapsibleComments is the default comments slot when post.atUri is
set.
Emits <link rel="site.standard.document" href={post.atUri}> at the top of the
rendered tree when post.atUri is set, eliminating the consumer-side layout
that previously hand-rolled the same tag (item 3 of PLAN-COMPOSABLE-LAYOUT.md).
Adds optional siteOrigin to BlogConfig for canonical URL construction in
forthcoming generateBlogPostMetadata (item 4).
Backward compat: existing callers (src/routes/blog-post-page.tsx) pass no
config and no slots, so the sidebar defaults to null and the rest fall back
to the new defaults — title, date line, and body still render. The body
default now wraps post.body in ReadMoreContent + MarkdownContent rather than
echoing post.description; this is the intentional behavior shift the plan
called out.
Tests walk the returned React element tree (mocking FeaturedSidebar to a sync
stub) and cover default slots, slot overrides, omission via null, classNames
merging, the notFound path, and the link-tag emission tied to atUri rather
than the comments slot.
Add slot-aware BlogPostLayout with default region components
Extracts BlogPostMeta, BlogPostCover, BlogPostBody as standalone exports so
consumers can wrap or restyle individual regions without forking the layout.
BlogPostLayout now accepts ReactNode slot props for meta, cover, title, body,
sidebar, and comments — undefined uses the default, null omits the region, a
ReactNode overrides it. FeaturedSidebar is the default sidebar when config is
supplied; CollapsibleComments is the default comments slot when post.atUri is
set.
Emits <link rel="site.standard.document" href={post.atUri}> at the top of the
rendered tree when post.atUri is set, eliminating the consumer-side layout
that previously hand-rolled the same tag (item 3 of PLAN-COMPOSABLE-LAYOUT.md).
Adds optional siteOrigin to BlogConfig for canonical URL construction in
forthcoming generateBlogPostMetadata (item 4).
Backward compat: existing callers (src/routes/blog-post-page.tsx) pass no
config and no slots, so the sidebar defaults to null and the rest fall back
to the new defaults — title, date line, and body still render. The body
default now wraps post.body in ReadMoreContent + MarkdownContent rather than
echoing post.description; this is the intentional behavior shift the plan
called out.
Tests walk the returned React element tree (mocking FeaturedSidebar to a sync
stub) and cover default slots, slot overrides, omission via null, classNames
merging, the notFound path, and the link-tag emission tied to atUri rather
than the comments slot.
Add Next-flavored BlogPostPage that emits blog.post.view telemetry
Wraps BlogPostLayout with a fire-and-forget after() call from next/server
that logs the post view via config.telemetry.logServerEvent. The
framework-agnostic version in src/routes/blog-post-page.tsx stays
slot-aware (all the new passthrough props: meta, cover, title, body,
sidebar, comments, beforeArticle, afterArticle, className, classNames)
but does not emit telemetry — consumers on @blog/rsc are expected to
emit themselves or live without blog.post.view.
src/next.tsx now imports BlogPostPage from ./next-blog-post-page while
BlogListPage continues to come from ./rsc; the other re-exports
(nextImageAdapter, NextBlogProvider, createBlogOpenGraphImage,
generateBlogPostMetadata) are unchanged.
Tests mock next/server's after, then assert the deferred callback fires
config.telemetry.logServerEvent with the expected payload when the post
resolves, that after is not scheduled when fetchDocument returns
undefined, and that a missing config.telemetry does not crash the
deferred callback.
port pure lib primitives + vitest coverage
- category-utils: getCategorySlug + makeCategoryTitleResolver (resolver
is config-bound, was a module-level map keyed off the consumer's
categories source)
- post-filtering-utils: calculateMatchScore, filterPostsByCategories
(OR-logic), sortPostsWithMatchScore (match-score tiebreaker only
when >1 selected), getAllCategoriesFromPosts
- client-post-filters: paginatePosts + filterSortAndPaginatePosts
- bluesky: URL parsing, handle resolution, post fetch (unchanged
surface from website)
- markdown-cache: loadMarkdownContent({contentDirPath}) — was a
baked-in path in the website
- 33 tests across the pure-function set (slug/title resolver edge,
match scoring, OR-filter, sort tiebreaker, pagination boundaries,
URL regex acceptance/rejection); skipping component renders and
fetch-glue per testing-philosophy.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>