Commits
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two-tab layout on /portfolio via ?tab=coding query param (academic
default, existing URLs unchanged). Coding projects reuse the shared
filter/sort/pagination machinery with a disjoint tag vocabulary;
school/course filters are academic-only. Project cards show tech
pills, GitHub/Tangled/live links, and private/closed-source badges.
Content lives in coding-projects.json and marketing-content.tsx.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prebuilt Vercel artifacts under .vercel/output were being linted,
causing false react-hooks/rules-of-hooks errors in minified chunks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bump atproto-presskit so a transient PDS failure surfaces as an uncached
error instead of an empty feed that gets ISR-cached as a 7-day 404.
Register the das-man-as-loss-function cover image and track the post
source published to the PDS on 2026-07-03.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Post bodies are sourced from the AT Protocol PDS at request time via
createAtprotoFeed. The staticFallback (postsData) only surfaced during a PDS
outage and had drifted from the PDS source of truth — it even carried 7
unpublished posts that never rendered in normal operation (now preserved as
drafts). Remove it along with the orphaned filesystem markdown chain.
- blog-config: drop staticFallback (feed defaults to [] on PDS failure)
- posts.ts: delete the postsData array; keep Post type + author/category re-exports
- delete unreferenced get-markdown.ts, markdown-cache.ts, load-markdown.ts
- CLAUDE.md: fix the stale "add a postsData entry" blog workflow
- bump atproto-presskit submodule (drops markdown-cache from @blog/server barrel)
- sync dependency versions (shiki, postcss, @types/node, @types/react, vercel)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sequoia.pub/subscribe now expects publicationUri/returnTo (camelCase);
the old pub/return names 400 with "Missing or invalid publication URI."
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Force-publishing the back-catalog backfilled `.content.text.markdown`
on the older PDS records and updated `lastPublished` timestamps in
the state file. Committing per the publish-state convention so
autoSync stays honest on future builds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up the scroll-preservation fix: category and sort toggles no
longer jump the viewport to the top. Pagination keeps default
scroll-to-top.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blogConfig is imported by client components (blog-list.tsx), which pulls in
blog-telemetry.ts with a static import of axiom/server. That module imports
@axiomhq/nextjs, which initializes AsyncLocalStorage at module eval time —
crashing in the browser since globalThis.AsyncLocalStorage doesn't exist.
Switch to dynamic import inside logServerEvent (only called from server code
via next/server after()), so the module is never evaluated in the browser.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
ConvexReactClient throws when given undefined, blocking static
generation when NEXT_PUBLIC_CONVEX_URL is not set at build time.
Runtime production sets the real URL; the placeholder is only used
during the static generation pass.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
NextBlogProvider reads URL search params for filter/sort/page state,
which requires a Suspense boundary during static generation in Next 15+.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
NEXT_PHASE=phase-production-build runs with NODE_ENV=production but does
not have access to runtime env vars in some deployment setups. Skip the
fail-fast throw during build so static generation succeeds; runtime
production still fails fast on missing token.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Break long lines in component props and function signatures to improve
readability and consistency with project formatting standards.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Client component that wraps submodule's BlogTelemetryProvider with local
Axiom integration for client-side blog events.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Update submodule reference to composable layout version. Also update
blog post frontmatter as needed.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Remove unused blog-context imports, trim markdown rendering, simplify
scroll logger. Update bluesky and local-draft utilities.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Trim blog author, comments, and subscribe wrappers to thin adapters
around submodule's Web Component integrations. Remove local filtering
and context dependencies.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Remove atproto-feed adapter, post filters, category utilities — all
now provided by submodule's createAtprotoFeed and filtering exports.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Remove hand-rolled Post component, BlogProvider context — all
replaced by submodule's BlogPostPage with slots and NextBlogProvider.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Remove pagination, filter, sort, and post list components — all
now provided by submodule's BlogListPage + useBlog hook.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add tagToCategoryMap export to categories.ts for submodule feed adapter.
Add DID to russAuthor for live Bluesky avatar resolution.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Remove [slug]/layout.tsx (link rel=site.standard.document now emitted
by BlogPostLayout upstream)
- Replace [slug]/opengraph-image.tsx with createBlogOpenGraphImage factory
- Move generateMetadata to [slug]/page.tsx as Next auto-discovers it there
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Replace hand-rolled Post component and page logic with submodule's
BlogPostPage. Use FeaturedSidebar slot for category-aware related posts.
Hoist post fetch for sidebar scope.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Replace hand-rolled pagination/filtering/layout with submodule's
BlogListPage component. RSC layer remains thin: fetches initial posts
and provides hero content and site chrome.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Create client component that wraps submodule's NextBlogProvider and
exposes filtering/pagination via useBlog hook.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Introduce centralized blog config that wires the submodule into the
consumer app and adapter for Axiom telemetry integration.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add path aliases and sync Tailwind source to include submodule components.
- Add @blog/* path aliases to tsconfig
- Include submodule src/ in Tailwind scanning
- Update package.json publish script with BLOG_REVALIDATE_URL
- Update dependencies via sync-deps.mjs
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Matches the post page ordering from 6034ebe so the OG image and the in-page
cover stay in sync. Local /src/assets fallback remains for posts without a
PDS coverImage blob.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover images broke after the PDS migrated from auriporia to lepista.
Look up the AtprotoPersonalDataServer endpoint from plc.directory instead
of hardcoding the host, so future migrations are picked up automatically
on the next revalidate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flip the cover image priority on the post page: post.coverImageUrl
(the PDS blob URL) renders first; coverImages[slug] (the local
StaticImageData mapping) is the fallback for posts where the PDS feed
didn't supply a cover — i.e., when fetchAllDocuments fell back to
postsData. Aligns image sourcing with the PDS-as-source-of-truth
model now that the feed is resilient.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switch getFeaturedPosts from the static postsData array to
fetchAllDocuments(), which is PDS-first with the new postsData
fallback. The "From the blog" sidebar now reflects new posts the
moment they're published via sequoia, without requiring a posts.ts
edit, and still degrades gracefully when the PDS times out.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap the listRecords fetch in an AbortController with a 20s timeout
and catch network/HTTP failures. On timeout or error, log a warning
and return the static postsData array (filtered + sorted the same way)
so the blog stays serviceable when the PDS is unreachable. PDS remains
the source of truth; posts.ts is now a resilience snapshot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The post markdown and JPG asset existed, but the lib/cover-images.ts
slug→StaticImageData mapping was missed, so the post page rendered
without its cover.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Keep personal docs, ad-hoc scripts, dialogue notes, intent-systems
experiments, and portfolio source files out of the repo. These exist
on the dev machine but aren't shipped with the site.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New essay arguing the atproto community should standardize on the @
mention as a pronoun for the person rather than a noun for the
infrastructure. Includes the OG image and the sequoia publish artifact
that minted the AT URI and Bluesky cross-post.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stock Convex tsconfig that scopes typechecking to ./**/* and excludes
the generated dir. Lets editor + tsc resolve the Convex function source
without leaking its compiler settings into the Next.js project.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Since the blog now reads from PDS at request time, content-only publishes
only need sequoia + revalidate — the Vercel build/deploy step is wasted
work. Make it opt-in via --deploy for the cases that also ship code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The post is being reworked; remove it from src/content/blog/ so it stops
appearing as a live route, and ignore the in-progress draft location and
the related project brief.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add local-draft loader that parses frontmatter and body from files on
disk so drafts can be previewed before sequoia mints an AT URI. Export
tagsToCategories from atproto-feed so the loader reuses the same tag
mapping as the PDS feed path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Detect bare bsky.app post URLs surrounded by blank lines and render them
as inline post cards (author, text with facet links, counts) instead of
plain links. Fetches via the public AppView at request time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The blog/[slug] page registers an after() callback that calls logEvent
+ logger.flush(). During next build's static-generation pass, Axiom's
no-store ingest fetch flipped the route to dynamic rendering, breaking
ISR (DYNAMIC_SERVER_USAGE).
Use ConsoleTransport during NODE_ENV=development and during
NEXT_PHASE=phase-production-build; keep AxiomJSTransport for runtime
production only. /blog/[slug] is now SSG (1w revalidate) again.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pin turbopack.root to the project directory so the stray ~/bun.lock
is no longer inferred as the workspace root.
- Add .avif and .webp to the proxy's logging exclusion list so static
image requests don't generate http.request events.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
next/image warned when h-8 was set without a matching width auto;
add w-auto so the rendered size matches the source aspect ratio.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Repo is now hosted at code.fugl.dev (Tangled). Footer and README link
there; the GitHub-shaped icon is replaced with Tangled's "dolly" mark.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After vercel deploy, call /api/revalidate to flush the Next.js data cache
so updated posts are live without waiting for the 7-day TTL to expire.
Skipped gracefully when REVALIDATE_SECRET is not set in the environment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New posts published via bun run publish now appear on the blog within
seconds of Vercel deployment, with no separate code deploy needed for
future posts.
- generateStaticParams and all server data fetches now use fetchAllDocuments
/ fetchDocument from atproto-feed instead of the local postsData array
- 7-day ISR (revalidate = 604800) on blog/[slug] routes; /api/revalidate
flushes the cache on publish
- post.tsx falls back to local markdown via getMarkdownContent when the PDS
record predates the markpub patch and has no content.text.markdown
- OG image generation tries the local src/assets/<slug>.jpg first, falls back
to PDS blob URL, then renders a text-only gradient card
- Featured post exclusion switched from numeric id to href so it works
regardless of which data source populated the post list
- Post type gains atUri, coverImageUrl, and contributors optional fields;
id changed from number to string to match AT Protocol rkeys
- next.config.ts remote patterns allow PDS blob hostnames
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Accepts a POST with x-revalidate-secret header matching REVALIDATE_SECRET
and calls revalidatePath on the blog routes, allowing a publish pipeline
to flush the 7-day ISR cache without a full redeploy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two-tab layout on /portfolio via ?tab=coding query param (academic
default, existing URLs unchanged). Coding projects reuse the shared
filter/sort/pagination machinery with a disjoint tag vocabulary;
school/course filters are academic-only. Project cards show tech
pills, GitHub/Tangled/live links, and private/closed-source badges.
Content lives in coding-projects.json and marketing-content.tsx.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bump atproto-presskit so a transient PDS failure surfaces as an uncached
error instead of an empty feed that gets ISR-cached as a 7-day 404.
Register the das-man-as-loss-function cover image and track the post
source published to the PDS on 2026-07-03.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Post bodies are sourced from the AT Protocol PDS at request time via
createAtprotoFeed. The staticFallback (postsData) only surfaced during a PDS
outage and had drifted from the PDS source of truth — it even carried 7
unpublished posts that never rendered in normal operation (now preserved as
drafts). Remove it along with the orphaned filesystem markdown chain.
- blog-config: drop staticFallback (feed defaults to [] on PDS failure)
- posts.ts: delete the postsData array; keep Post type + author/category re-exports
- delete unreferenced get-markdown.ts, markdown-cache.ts, load-markdown.ts
- CLAUDE.md: fix the stale "add a postsData entry" blog workflow
- bump atproto-presskit submodule (drops markdown-cache from @blog/server barrel)
- sync dependency versions (shiki, postcss, @types/node, @types/react, vercel)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Force-publishing the back-catalog backfilled `.content.text.markdown`
on the older PDS records and updated `lastPublished` timestamps in
the state file. Committing per the publish-state convention so
autoSync stays honest on future builds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blogConfig is imported by client components (blog-list.tsx), which pulls in
blog-telemetry.ts with a static import of axiom/server. That module imports
@axiomhq/nextjs, which initializes AsyncLocalStorage at module eval time —
crashing in the browser since globalThis.AsyncLocalStorage doesn't exist.
Switch to dynamic import inside logServerEvent (only called from server code
via next/server after()), so the module is never evaluated in the browser.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
NEXT_PHASE=phase-production-build runs with NODE_ENV=production but does
not have access to runtime env vars in some deployment setups. Skip the
fail-fast throw during build so static generation succeeds; runtime
production still fails fast on missing token.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Remove [slug]/layout.tsx (link rel=site.standard.document now emitted
by BlogPostLayout upstream)
- Replace [slug]/opengraph-image.tsx with createBlogOpenGraphImage factory
- Move generateMetadata to [slug]/page.tsx as Next auto-discovers it there
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add path aliases and sync Tailwind source to include submodule components.
- Add @blog/* path aliases to tsconfig
- Include submodule src/ in Tailwind scanning
- Update package.json publish script with BLOG_REVALIDATE_URL
- Update dependencies via sync-deps.mjs
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Cover images broke after the PDS migrated from auriporia to lepista.
Look up the AtprotoPersonalDataServer endpoint from plc.directory instead
of hardcoding the host, so future migrations are picked up automatically
on the next revalidate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flip the cover image priority on the post page: post.coverImageUrl
(the PDS blob URL) renders first; coverImages[slug] (the local
StaticImageData mapping) is the fallback for posts where the PDS feed
didn't supply a cover — i.e., when fetchAllDocuments fell back to
postsData. Aligns image sourcing with the PDS-as-source-of-truth
model now that the feed is resilient.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switch getFeaturedPosts from the static postsData array to
fetchAllDocuments(), which is PDS-first with the new postsData
fallback. The "From the blog" sidebar now reflects new posts the
moment they're published via sequoia, without requiring a posts.ts
edit, and still degrades gracefully when the PDS times out.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap the listRecords fetch in an AbortController with a 20s timeout
and catch network/HTTP failures. On timeout or error, log a warning
and return the static postsData array (filtered + sorted the same way)
so the blog stays serviceable when the PDS is unreachable. PDS remains
the source of truth; posts.ts is now a resilience snapshot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New essay arguing the atproto community should standardize on the @
mention as a pronoun for the person rather than a noun for the
infrastructure. Includes the OG image and the sequoia publish artifact
that minted the AT URI and Bluesky cross-post.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add local-draft loader that parses frontmatter and body from files on
disk so drafts can be previewed before sequoia mints an AT URI. Export
tagsToCategories from atproto-feed so the loader reuses the same tag
mapping as the PDS feed path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The blog/[slug] page registers an after() callback that calls logEvent
+ logger.flush(). During next build's static-generation pass, Axiom's
no-store ingest fetch flipped the route to dynamic rendering, breaking
ISR (DYNAMIC_SERVER_USAGE).
Use ConsoleTransport during NODE_ENV=development and during
NEXT_PHASE=phase-production-build; keep AxiomJSTransport for runtime
production only. /blog/[slug] is now SSG (1w revalidate) again.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New posts published via bun run publish now appear on the blog within
seconds of Vercel deployment, with no separate code deploy needed for
future posts.
- generateStaticParams and all server data fetches now use fetchAllDocuments
/ fetchDocument from atproto-feed instead of the local postsData array
- 7-day ISR (revalidate = 604800) on blog/[slug] routes; /api/revalidate
flushes the cache on publish
- post.tsx falls back to local markdown via getMarkdownContent when the PDS
record predates the markpub patch and has no content.text.markdown
- OG image generation tries the local src/assets/<slug>.jpg first, falls back
to PDS blob URL, then renders a text-only gradient card
- Featured post exclusion switched from numeric id to href so it works
regardless of which data source populated the post list
- Post type gains atUri, coverImageUrl, and contributors optional fields;
id changed from number to string to match AT Protocol rkeys
- next.config.ts remote patterns allow PDS blob hostnames
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>