Repository navigation
Add /blog with initial positioning posts - #800
Conversation
|
Warning Review limit reached
Next review available in: 16 seconds Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis change adds a Markdown-backed blog with catalog parsing, seven posts, HTML and JSON routes, RSS output, post OG images, client navigation and rendering, public blog OG metadata, and E2E/unit coverage. ChangesBlog publishing and delivery
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant BlogRoute
participant blogRouteLoader
participant BlogApiHandler
participant BlogCatalog
Browser->>BlogRoute: navigate to /blog
BlogRoute->>blogRouteLoader: load blog data
blogRouteLoader->>BlogApiHandler: request /blog.json
BlogApiHandler->>BlogCatalog: listBlogPosts()
BlogCatalog-->>BlogApiHandler: post summaries
BlogApiHandler-->>blogRouteLoader: JSON payload
blogRouteLoader-->>BlogRoute: loader data
BlogRoute-->>Browser: render blog post links
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ 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 |
|
🔎 Preview deployed: https://kody-pr-800.kody-a99.workers.dev Worker: Mocks:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/worker/client/routes/blog-post.tsx`:
- Around line 69-93: Update loadPost and its queueTask callback to accept and
propagate an AbortSignal through the blog-post request, following the existing
app.tsx session-refresh queueTask pattern. Pass the signal to fetch and ensure
superseded requests are aborted or ignored before updating post or status,
preserving the latest slug/href result.
In `@packages/worker/client/routes/blog.tsx`:
- Around line 47-98: Update packages/worker/client/routes/blog.tsx#L47-L98 and
packages/worker/client/routes/blog-post.tsx#L69-L129: accept the AbortSignal in
each handle.queueTask callback, thread it through loadBlog/loadPost into fetch,
and return immediately after fetch resolves if the signal is aborted. Ensure
aborted tasks do not modify posts, post, or status and do not mark the load
latch as loaded or failed.
- Around line 47-65: The blog route’s queued load drops cancellation, allowing
stale fetches and state updates after navigation. Update the
loadBlog/handle.queueTask flow to accept the provided AbortSignal, pass it to
fetch, and check for abortion before applying posts/status or reporting task
success/failure, preserving only updates for the active request.
🪄 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 Plus
Run ID: 2ddc7301-8bdc-4e23-a017-002aee708fbf
📒 Files selected for processing (31)
e2e/og-images.spec.tspackages/worker/client/app.tsxpackages/worker/client/routes/blog-post.tsxpackages/worker/client/routes/blog.tsxpackages/worker/client/routes/index.tsxpackages/worker/src/app/blog-display.tspackages/worker/src/app/handlers/blog-post-og-image.node.test.tspackages/worker/src/app/handlers/blog.tsxpackages/worker/src/app/loader-data.tspackages/worker/src/app/router.tspackages/worker/src/app/routes.tspackages/worker/src/blog/catalog.node.test.tspackages/worker/src/blog/catalog.tspackages/worker/src/blog/markdown.d.tspackages/worker/src/blog/og-image.tspackages/worker/src/blog/parse-frontmatter.tspackages/worker/src/blog/posts/every-install-is-a-fork-you-own.mdpackages/worker/src/blog/posts/gateways-connect-homes-accumulate.mdpackages/worker/src/blog/posts/self-service-means-ownership.mdpackages/worker/src/blog/posts/the-assistant-that-cant-leak-your-keychain.mdpackages/worker/src/blog/posts/the-automations-you-never-built.mdpackages/worker/src/blog/posts/your-assistants-home.mdpackages/worker/src/blog/posts/zero-inference-calls.mdpackages/worker/src/blog/rss.tspackages/worker/src/og/page-image.node.test.tspackages/worker/src/og/pages.tspackages/worker/tsconfig-client.jsonpackages/worker/wrangler.jsonctools/vite-markdown-as-text.tsvitest-shared.tsvitest.config.ts
| async function loadBlog() { | ||
| status = 'loading' | ||
| handle.update() | ||
| try { | ||
| const response = await fetch(blogApiPath, { | ||
| headers: { Accept: 'application/json' }, | ||
| }) | ||
| const payload = await readJson<BlogLoaderData>(response) | ||
| if (!response.ok || !payload?.ok) { | ||
| throw new Error('Unable to load blog posts.') | ||
| } | ||
| posts = payload.posts | ||
| status = 'ready' | ||
| handle.update() | ||
| } catch { | ||
| status = 'error' | ||
| handle.update() | ||
| } | ||
| } | ||
|
|
||
| return () => { | ||
| const currentHref = readCurrentRouterHref(handle) | ||
| if (!isBlogIndexPath(currentHref)) { | ||
| return <section mix={css(pageCss)} /> | ||
| } | ||
|
|
||
| const routeData = tryConsumeRouteLoaderData(handle, 'blog', currentHref) | ||
| const appliedRouteData = Boolean(routeData?.ok) | ||
| if (routeData?.ok) { | ||
| posts = routeData.posts | ||
| status = 'ready' | ||
| loadLatch.markLoaded(currentHref) | ||
| } | ||
|
|
||
| const needsStaleRefresh = consumeStaleNavigationData(currentHref) | ||
| const needsLoad = loadLatch.needsLoad({ | ||
| currentHref, | ||
| isLoading: status === 'loading', | ||
| appliedRouteData, | ||
| needsStaleRefresh, | ||
| }) | ||
| if (needsLoad && typeof document !== 'undefined') { | ||
| handle.queueTask(async () => { | ||
| try { | ||
| await loadBlog() | ||
| if (status === 'ready') loadLatch.markLoaded(currentHref) | ||
| else loadLatch.markFailed(currentHref) | ||
| } catch { | ||
| loadLatch.markFailed(currentHref) | ||
| } | ||
| }) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Client-side retry loaders don't cancel superseded fetches. handle.queueTask appears to provide an AbortSignal to its callback — app.tsx's session-refresh usage (handle.queueTask(async (signal) => { ...; if (signal.aborted) return })) relies on it — but both blog route files omit it entirely, so a stale fetch can resolve after a newer navigation and overwrite posts/post/status with outdated data, or incorrectly mark a still-valid href as failed in the load latch.
packages/worker/client/routes/blog.tsx#L47-L98: acceptsignalin thequeueTaskcallback, pass it intoloadBlog'sfetch, and bail out (without touchingstatus/postsor the load latch) ifsignal.abortedonce the fetch resolves.packages/worker/client/routes/blog-post.tsx#L69-L129: same fix — thread thequeueTasksignal intoloadPost'sfetchand checksignal.abortedbefore committingpost/status/load-latch updates.
📍 Affects 2 files
packages/worker/client/routes/blog.tsx#L47-L98(this comment)packages/worker/client/routes/blog-post.tsx#L69-L129
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/worker/client/routes/blog.tsx` around lines 47 - 98, Update
packages/worker/client/routes/blog.tsx#L47-L98 and
packages/worker/client/routes/blog-post.tsx#L69-L129: accept the AbortSignal in
each handle.queueTask callback, thread it through loadBlog/loadPost into fetch,
and return immediately after fetch resolves if the signal is aborted. Ensure
aborted tasks do not modify posts, post, or status and do not mark the load
latch as loaded or failed.
There was a problem hiding this comment.
Fixed in 8697977: both queueTask callbacks now accept the provided AbortSignal, thread it into the loadBlog/loadPost fetches, and bail before committing posts/post/status or load-latch updates when aborted (including the catch paths, so an abort never sets status = 'error').
There was a problem hiding this comment.
Skipped: comment is from another GitHub bot.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d56920b. Configure here.

Adds a public
/blogto heykody.dev and publishes seven initial positioning posts by Kent C. Dodds.What's here
Engineering
/blogindex and/blog/:slugpost pages in the Remix 3 client, following the existing public-route patterns (privacy for the static index shape, community-detail for dynamic pages with 404 +OgHead+loaderDatahydration)..jsonAPI endpoints back SPA navigation like community does.packages/worker/src/blog/posts/, bundled into the Worker via a WranglerTextmodule rule (plus a tiny Vite plugin so vitest resolves.mdas text). A small typed catalog (catalog.ts) parses frontmatter (title,date,description,order) and sorts date desc, then order asc. Adding a post = drop a.mdfile + one import/entry in the catalog./blog/:slug/og.pngusing the existing Satori primitives inpackages/worker/src/og/(lazy-imported renderer per the sanctioned cold-start pattern), and a staticblogentry in the OG page registry for the index./blog/rss.xml(escaped, summary descriptions,<link rel="alternate">on the index).e2e/og-images.spec.tsextended for/blogand post OG images.Content (7 posts, author Kent C. Dodds)
{{secret:name}}resolution at the fetch gateway, per-user Durable Object isolation, honest limits.All posts were drafted against the Kent writing voice skill (blog register, ASCII punctuation, no em dashes) and reviewed against the positioning canon. No fabricated quotes, users, or metrics; the only numbers cited are Kent's public dogfooding stats from the June 2026 "I built my own OpenClaw" video (42 jobs, 55 packages, 51 secrets, 11 OAuth integrations, 72 built-in capabilities) and the $0/$5 plan prices already shown in the product UI. Every post links to the signup/waitlist page and relevant docs.
Orchestration note
Work was delegated per instructions: implementation to a Grok 4.5 subagent (
cursor-grok-4.5-high) and writing to Sonnet 5 subagents. "Sonnet 5" was not an exact available model slug in this environment; the nearest available slug used wasclaude-fable-5-thinking-high.Validation
Full
npm run validatepasses locally (format, lint, typecheck, 1010 unit tests, Playwright e2e, MCP e2e). Also smoke-tested againstnpm run dev:/blog(200, lists all 7 posts),/blog/:slug(200, SSR'd body + OG meta),/blog/:slug/og.png(200, image/png, verified visually),/blog/rss.xml(200, valid escaped XML), unknown slug (404).System recap — extends app-ui (medium risk)
Mode: recap · Base:
main@07e85d84· Head:a70da330Classification: extends — the browser app / route surface gains a new public section (
/blogindex, post pages, post OG images, RSS). New blog content module underpackages/worker/src/blog/composes existing OG and SSR primitives; no primitives added to the map (repo-backed static content, no storage or auth changes).Primitives touched
app-uiblog,blogPost,blogPostOgImage,blogRss(+.jsonAPIs), handlers, client routes, nav linkSystem map
A request for a blog page flows from the route contract through new blog handlers into the markdown catalog, reusing the existing SSR document and Satori OG renderer.
Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).
Invariants
No auth or per-user data involved: all blog routes are public, read-only, and serve repo-bundled content. No userId-scoped paths touched.
Summary by CodeRabbit