Changelog
What's changed on this site recently. Full commit log on GitHub.
-
🎧 badges now render on tag + category pages
The narration indicator was only threaded through on the homepage and /posts listing. Tag + category pages showed the cards but no badges. Extracted the batch narration lookup into a shared helper so all five listing surfaces render badges consistently.
-
Fixed site search returning 401 for anonymous visitors
EmDash's built-in search endpoint requires content:read scope, so typing in the nav search box silently returned zero results for everyone not logged in. Shipped /api/search.json as a public proxy with scored results; intercepted the client-side fetch so the existing UI keeps working. Real-browser verified via Playwright.
-
Cmd+K keyboard shortcut for search
Standard everywhere — now here too. ⌘K (or Ctrl+K on Linux/Windows) focuses the nav search from anywhere on the page; Esc blurs back out. Visible ⌘K badge inside the input disappears while typing.
-
Reading progress + back-to-top on post pages
Thin accent-colored strip at the top of the viewport fills as you scroll through an article. Circular back-to-top button fades in past 700px scroll. Both driven by a single rAF-throttled handler.
-
Podcast listing: duration preview, 500KB lighter page
Episode rows now show approximate duration (6:45, 3:43, etc.) in the meta before clicking play — computed from MP3 byte size. Pair change: preload='none' on every episode <audio> since we no longer need metadata to render the duration. Saves ~500KB per page load.
-
Manual dark/light theme toggle
Sun/moon button in the nav flips the theme and persists preference for a year. Returning visitors get their theme on first paint — no flash.
-
First successful scheduled backup in weeks
Cron-driven D1 backups had been silently failing with HTTP 522 because the Worker's scheduled() handler tried to fetch its own public hostname. Refactored to call the shared runBackup module directly; all 19 tables now dump to R2 every hour. 5 regression tests lock in the shape.
-
Public operational status page
/status now shows cron health, content counts, and every discovery URL in one place. Admins get a green/yellow/red dot right in the nav via /api/cron-health.json.
-
WCAG AA across light + dark mode
Ran axe-core against 9 pages in both color schemes. Went from 19 serious violations to 0. Gruvbox palette darkened where needed to clear 4.5:1 contrast on both bg and surface colors.
-
6-script real-browser verify pipeline
pnpm run verify now drives Playwright + axe-core + link crawler + feed validator. Every deploy gates on vitest (90 tests). Full pipeline in scripts/smoke*.mjs.
-
Blog → Podcast
Every AI-narrated post is now a podcast episode. /podcast.xml is iTunes-compliant (with Podcast 2.0 transcripts). /podcast landing page lets humans subscribe via Apple / Overcast / Pocket Casts. /rss.xml and /atom.xml also carry audio enclosures.
-
Browser SpeechSynthesis fallback on silent posts
Every published post now has a playable audio option. If the MiniMax narration isn't ready yet, a 'Read aloud' button uses the browser's Web Speech API instead — clear UX rather than silent omission.
-
Agent discovery surfaces
Shipped /openapi.json (with x-x402-payment extension), exposed the built-in EmDash MCP server, added Organization + OfferCatalog + Person + PodcastSeries + ItemList JSON-LD, and linked it all from /docs/agents. Every paid surface is discoverable programmatically.
-
Three-layer LCP optimization
Hero image now preloads in the <head>, loads eagerly with fetchpriority=high, while below-fold grid cards stay lazy. Hover-prefetch on desktop internal links drops click-to-paint latency to near zero.
-
PWA manifest + iOS meta
Site is now installable as an app on iOS, Android, and Chrome desktop. Home-screen shortcuts jump straight to Posts, Podcast, or Skills.