vibescoder

Friday Fixes: This Week's Minor Site Improvements

·10 min read

After a week of publishing meaty infrastructure posts, the site itself needed attention. Small bugs that I'd been ignoring, missing social previews, no way to know when someone left a comment. The kind of stuff that piles up when you're focused on content instead of the platform.

So I sat down with Coder Agents and knocked out nine improvements in a single conversation. No local IDE, no task switching. Just describing problems and letting the agent fix them, commit, and deploy. Here's every fix, what caused it, and what I learned.

1. Code Block Overflow on Mobile

The problem: On mobile, long lines in code blocks were clipping instead of scrolling. The [OK] GPU: NVIDIA GeForce RTX 5090 (32607 MiB... line in the home lab post was getting cut off with no scrollbar.

Root cause: The <pre> component had overflow-x-auto, but the <code> element inside it was inheriting styles from the shared InlineCode component: background, border, padding, smaller font size. That created a clipping box inside the scrollable container.

The fix: One line in MDXComponents.tsx. Added Tailwind [&>code] child selectors on the Pre component to reset background, border, padding, border-radius, font size, and text color when code appears inside pre.

The kind of bug where the symptom (clipped text) and the cause (inline code styling leaking into block code) live in completely different mental models. The agent traced it immediately.

2. Mobile Table Layout

The problem: The four-column model table in the Coder Agents post was unusable on mobile. The "Why" column was wrapping to a single word per line.

The fix: Dropped the column entirely. The reasoning for each model already existed in the "Why These Models" section above the table. Three columns fit cleanly on any screen width.

Sometimes the best fix is removing content, not adding responsive tricks.

3. Social Metadata Overhaul

The problem: Sharing a blog post URL in Slack showed the wrong preview. The site-wide defaults ("Vibes Coder" with the old green-bar OG image) were appearing instead of the post's actual title and description.

Two root causes:

  1. The post page's generateMetadata only returned title and description without explicit openGraph or twitter tags. Social crawlers fell back to the layout-level defaults.
  2. Those defaults still had old branding from before the rebrand.

The fixes:

  • Post generateMetadata now emits full openGraph (title, description, URL, article type, published date) and twitter card metadata.
  • All site-wide metadata updated from "Vibes Coder" to "vibescoder" with the current description.
  • Regenerated opengraph-image.png with the purple waveform, vibes/coder wordmark, and dark background matching the current design system. Used sharp (already in the Next.js dependency tree) with an SVG-to-PNG pipeline.
  • RSS feed title updated to match.
  • Rendered a proper 512x512 favicon PNG from the waveform icon for platforms that need a raster favicon.

4. Dynamic OG Images per Post

The static OG image fix got sharing working, but every post shared the same generic card. For a site that publishes multiple posts a week, each link in Slack or Twitter should show its own title card.

The solution: A Next.js opengraph-image.tsx route inside posts/[slug]/ that generates a branded 1200x630 social card per post using ImageResponse. Each card includes:

  • The post title (large, bold)
  • Formatted date and reading time
  • The purple waveform mark
  • vibes / coder wordmark at the bottom

If the post contains images, the first one is composited as a subtle background at 15% opacity behind the title card. Posts without images get a clean branded card. The site-level opengraph-image.png remains the fallback for non-post pages like the homepage and about page.

Static params are generated at build time, so every published post gets its own card with zero runtime cost.

5. Giscus Comment Notifications in Slack

The problem: No way to know when someone leaves a blog comment. Giscus backs onto GitHub Discussions, but the repo owner isn't auto-subscribed to discussions that Giscus creates on behalf of commenters.

The solution: A GitHub Actions workflow (.github/workflows/giscus-notify.yml) that triggers on discussion_comment created events, filtered to the Announcements category. Sends a Slack Block Kit message via incoming webhook with:

  • Commenter avatar and username
  • Comment body (truncated to 500 chars)
  • "Reply on GitHub" button that deep-links to the comment
  • "View on Blog" button constructed from the discussion title

The comment body is read from $GITHUB_EVENT_PATH via jq rather than ${{ }} expression interpolation to avoid shell injection.

Gotcha: Giscus creates discussion titles as posts/my-slug (no leading slash), not /posts/my-slug. Both the notification workflow's URL construction and the comment count matching had to account for this.

6. Comment Count Badges

The problem: No visual indication of engagement on the homepage. A post with ten comments looked identical to one with zero.

The solution: A new discussions.ts module fetches comment counts from the GitHub Discussions REST API. It's a public endpoint (no auth needed, 60 req/hr rate limit) cached with next: { revalidate: 300 } for five-minute staleness. Returns a Record<string, number> mapping slugs to counts.

The homepage fetches posts and comment counts in parallel via Promise.all, merges them, and passes the data to PostCard. Cards now show a chat bubble icon and count in the metadata line (next to date and reading time) when a post has one or more comments.

Small touch, but it gives readers a reason to click through to posts that have an active conversation.

7. Slack /todo Slash Command

The problem: Updating the project TODO list meant opening GitHub, navigating to the content repo, editing content/TODO.md, and committing. Way too much friction for capturing a quick idea.

The solution: A Vercel API route at /api/slack/todo that receives Slack slash commands, verifies the HMAC-SHA256 signature, and appends items to TODO.md via the GitHub Contents API.

/todovc Fix the RSS feed          → adds to "Up Next"
/todovc backlog: Explore MCP      → adds to "Ideas / Backlog"

The route parses the command text for an optional backlog: prefix, fetches the current TODO.md (including its SHA for optimistic concurrency), inserts - [ ] item at the end of the target section, and commits with a descriptive message.

Security: Slack signs every request with a shared signing secret. The route verifies the signature using crypto.createHmac with timing-safe comparison. No valid signature, no write. Reuses the existing GITHUB_TOKEN that the build pipeline already has, so no new PAT was needed.

Bugs hit during implementation:

  1. TypeScript's /s (dotAll) regex flag isn't available below es2018 target. Vercel's tsconfig targets below that. Fixed with [\s\S] equivalent.
  2. The auth middleware was blocking all /api/* routes with an admin session check. Slack doesn't send cookies, so it got a 401 before the route handler ever ran. Fixed by adding /api/slack/* to the middleware allowlist. Safe because the route handles its own auth via HMAC.

8. CollapsibleCode Label Fix and Collapsed Preview

The problem: The <CollapsibleCode> component wasn't showing its label. In MDX, every usage passes a label prop, but the component was expecting title.

Root cause: A prop name mismatch. The component was built with title, but when it was used in actual blog content, the natural prop name was label. Nobody noticed because the component still expanded and collapsed fine; you just couldn't see what it contained until you clicked.

The fixes:

  • Accept both label (preferred) and title (deprecated, for backward compat).
  • Added a faded preview of the first few lines when collapsed. Uses max-h-24 with a gradient overlay that fades to the surface color. Readers can see a glimpse of the code before deciding to expand, instead of staring at a blank clickable bar.

9. Slack App Wiring

This isn't a code change, but it's worth documenting because it took more steps than you'd think. The Slack app connects two features: incoming webhooks for comment notifications (feature 5) and the /todovc slash command (feature 7).

  1. Created a Slack app from scratch (not from manifest, since all we needed was a webhook and a slash command)
  2. Incoming Webhooks: toggled on, added to #vibes-coder channel
  3. Slash Commands: created /todovc, pointed at https://vibescoder.dev/api/slack/todo
  4. Added SLACK_WEBHOOK_URL as a GitHub repo secret (for the Actions notification workflow)
  5. Added SLACK_SIGNING_SECRET as a Vercel env var (for slash command verification)
  6. Redeployed on Vercel (env vars don't hot-reload on running deployments)

Two env vars, one Slack app, two completely different integrations. Took longer to navigate the Slack admin UI than to write the code.

What I Learned

Small bugs compound. None of these were urgent individually. But collectively they meant: posts looked bad when shared, mobile readers hit clipped code, comments happened in silence, and capturing ideas required six clicks. Knocking them all out in one session changed how the site feels to use.

Agents are great at the boring middle. Writing the Slack HMAC verification, wiring up GitHub API calls for TODO.md, tracing a CSS inheritance chain through three component layers. These are tasks that require attention to detail but not creative judgment. Perfect agent territory.

Prop mismatches are the new typo. The CollapsibleCode bug was invisible for days because the component worked, it just didn't show its label. When AI generates components and AI writes the MDX that uses them, a prop name drift between title and label is the kind of thing neither side catches unless you're reading the rendered output carefully.

Dynamic OG images are worth the effort. Every post shared on Slack or Twitter now has its own branded card with the title, date, and reading time. It took one file and zero runtime cost (statically generated at build time). If you're running a Next.js blog and sharing links regularly, this is a high-leverage addition.


Files Changed

  • src/components/MDXComponents.tsx — code block overflow fix
  • src/components/CollapsibleCode.tsx — label prop fix + collapsed preview
  • src/app/layout.tsx — metadata branding update
  • src/app/posts/[slug]/page.tsx — per-post social metadata
  • src/app/posts/[slug]/opengraph-image.tsx — new, dynamic OG images
  • src/app/opengraph-image.png — regenerated site-level OG image
  • src/lib/discussions.ts — new, comment count fetcher
  • src/lib/types.ts — added commentCount to Post
  • src/app/page.tsx — comment counts merged into post list
  • src/components/PostCard.tsx — comment count badge
  • src/app/api/slack/todo/route.ts — new, slash command endpoint
  • src/middleware.ts — Slack routes added to auth allowlist
  • .github/workflows/giscus-notify.yml — new, comment notification workflow
  • public/images/branding/favicon-512x512.png — new, raster favicon

What's Next

The home lab has two new models queued up for testing. Kimi K2.6 is Moonshot AI's latest, and early benchmarks put it in competitive territory with the frontier cloud models. The collective wisdom is that I should do API-only for now (too large for consumer VRAM), but worth profiling on the homelab rig against the cloud options we benchmarked in the model showdown. Any guesses on if we can crack 2 tokens/sec? Gemma 4 from Google is the other one on the list. We're still researching whether it fits in 32 GB of VRAM, but Google's been making noise about 6x VRAM efficiency improvements, so it might land squarely in home lab territory.

Both were requested by the dev team after reading the showdown results. Round 2 is coming.

By the Numbers

  • 9 improvements shipped in one conversation
  • 14 files changed across both repos
  • 3 new features (dynamic OG images, comment notifications, /todo command)
  • 4 bug fixes (code overflow, table layout, social metadata, CollapsibleCode label)
  • 2 Slack integrations from 1 Slack app
  • 1 new env var needed (SLACK_SIGNING_SECRET; everything else reused existing tokens)
  • 0 new dependencies added
  • 5-minute cache on comment counts
  • 0 runtime cost for OG image generation (static at build time)

Comments