Changelog
7 wydańv1.6.0 — comments on projects, LinkedIn reliability fixes, and an a11y pass
Nine PRs since v1.5.0: project pages get comments, the LinkedIn publish pipeline gets more reliable, a full site-wide polish pass, and a homepage accessibility fix driven by a WCAG audit.
New: comments on project pages
The existing LinkedIn-authenticated, Neon-backed comments component (already used on blog posts) now shows on every project detail page too.
New: LinkedIn publish pipeline reliability
Posts with no explicit images now fall back to their generated OG image when published to LinkedIn, instead of going out as a bare text/link card. A new weekly scheduled workflow checks
LINKEDIN_ACCESS_TOKENhealth before it expires silently.Fixed: per-post OG image and meta description
Next 16 passes
paramsas a Promise; the OG-image route was still reading it synchronously, so every post's social card silently fell back to the generic site-wide card. Also fixed the meta description ignoring the hand-writtenlinkedin_descriptionfrontmatter field.Fixed: homepage accessibility (WCAG AA)
Follow-up from an
/impeccable critiquepass — fixed CTA and label-mono contrast failures, added a sitewide focus-visible ring, fixed "Selected work" always showing two AI projects instead of one AI + one DevOps, and smoothed a scroll-reveal flash.Polish: site-wide pass
A branded 404 page, i18n fallback-text bugs, equal-height grid card layout, a lightbox scroll-lock fix, and a round of visual amplification (ambient hero glows, card hover depth, a live clock in the nav).
Also
- Gallery/avatar images switched to
next/image(screenshot thumbnails now serve as WebP instead of raw JPG/PNG) - Screenshots and updated copy for the booking-assistant project
- New post: "Two Months of OpenCode Habits, Gone in a Few Days"
- Gallery/avatar images switched to
v1.5.0 — share buttons, a real test suite, and Next.js 16
Four PRs since v1.4.0: posts get social-share buttons, the codebase finally has a real test suite with a CI coverage gate, CI moved off the Node 20 runtime GitHub is deprecating, and the whole site is now running on Next.js 16.
New: share buttons on posts
Every post now ends with LinkedIn, Facebook, and X share links, using the post's real canonical URL and title so shares land on the right page with a correct preview — styled to match the existing panel/label-mono conventions rather than bolted-on brand buttons.
New: a real test suite, with a coverage gate in CI
Before this, only 5 files had tests, all pure
lib/logic — nothing else. Now: 213 tests across 42 files.- Every remaining testable
lib/module (comments, admin-posts, github-content, openrouter, changelog, seo, structured-data, projects, and more), mocking external deps (Neon, fetch, fs, auth) the same way the existing tests already did. - Every
app/api/**route handler, plus the sitemap/robots/RSS generators and the two static-file-serving routes — including regression tests for their path-traversal guard (../../etc/passwd-style requests must 404). - Every interactive component — forms, search, comments, the project gallery lightbox, theme/locale switching.
- CI's
verifyjob now enforces coverage thresholds (a floor, not a target) and uploads the HTML/lcov report as a build artifact. Deliberately skips purely static/presentational components and the large admin post-editor form — flagged as a known gap rather than silently excluded.
Upgraded: Node 24 and current GitHub Action versions
GitHub is deprecating the Node 20 runtime actions run on — forced to Node 24 by mid-2026, removed entirely by September. Bumped
node-versionto 24 across every workflow, andactions/checkout,actions/setup-node,actions/upload-artifact, andpeter-evans/create-pull-requestto their current majors (all now default to Node 24).Also fixed the one cleanly-fixable
npm auditfinding:next'ssharpdependency was pinned below a version with known libvips CVEs, patched via apackage.jsonoverridespin without touching Next's own version.Upgraded: Next.js 15 → 16
middleware.ts→proxy.ts(Next 16's rename of the special file — the actualnext-intlmiddleware logic didn't change).- Turbopack is now the default bundler in both
next devandnext build. tsconfig.json'sjsxsetting moved toreact-jsx(now mandatory — Next 16 uses the React automatic runtime).- The new
cacheComponentsopt-in caching model was deliberately left off — it's an all-or-nothing experimental flag, not a default, so nothing about how posts/projects/tags get statically generated changed.
Also
@types/nodebumped to match the Node 24 target.
- Every remaining testable
v1.4.0 — project OG images, Lighthouse CI budget, and a changelog page
Three PRs since v1.3.0: project pages get their own social-share cards, Lighthouse now runs as a CI budget, and there's a changelog page — which, fittingly, is where this note is going to live from now on.
New: dynamic OG images for project pages
Posts already generated a real per-post
og:imagecard (title + tags on the branded background); project pages fell back to the generic sitewide banner regardless of which project was actually linked.app/[locale]/projects/[slug]/opengraph-image.tsxmirrors the post route, so a shared project link now shows its own title and tags.New: Lighthouse CI budget
A
lighthousejob now runs after everyverifypass, checking the homepage, posts index, projects index, and tags index withtreosh/lighthouse-ci-action.- Accessibility, best-practices, and SEO scores fail the build below 0.9 — these are static/DOM checks, not timing-dependent, so they don't flake on shared runners.
- Performance score and Core Web Vitals (LCP, CLS, TBT) are warn-only. Timing on a shared GitHub-hosted runner is noisy enough that treating it as a hard gate would mean red builds for reasons that have nothing to do with the code.
- Reports upload to Lighthouse's temporary public storage, so every CI run gets a shareable report link without hosting anything.
New: changelog page
/changelog(both locales) reads this repo's GitHub Releases at build time and renders each one's real title, date, and notes — through the sameremarkpipeline already used for post bodies. Nothing to hand-maintain: cut a release the way this project always has, and it shows up on the site on the next deploy. Statically generated, linked from the nav and footer, included in the sitemap.Also
automated-publish-pipeline's project card updated with the Sentry error-monitoring bullet that had been shipped in v1.3.0 but not yet described on the site.
v1.3.0 — RSS, analytics, light/dark theme, tags, and error monitoring
Six PRs since v1.2.0: an RSS feed and real analytics, a full light/dark theme, three new ways to browse the log (reading progress, related posts, tags), and error monitoring across all three runtimes.
New: RSS feed + Web Analytics
/feed.xml— a real RSS feed for the log, so readers (and feed aggregators) don't have to check back manually.- Vercel Web Analytics wired in via
<Analytics />. Initially shipped importing from@vercel/analytics/react, which silently recorded zero real pageviews in production — fixed to@vercel/analytics/next, the App Router–correct import.
New: light/dark theme
- Every color is now a CSS custom property (
--color-ink,--color-paper, etc.) instead of a fixed hex value, with a light palette alongside the existing dark one — one toggle in the nav switches both. - A synchronous init script sets the theme before hydration so there's no flash of the wrong theme on load, with
suppressHydrationWarningon<html>to avoid a spurious mismatch warning from the necessarily-late attribute it sets. - Tailwind Typography's post-body styling now reads from the same tokens (
theme.extend.typographyintailwind.config.ts) instead of a hardcodedprose-invert, so post content matches whichever theme is active instead of staying dark-only.
New: reading progress, related posts, and tags
- A thin scroll-tracked progress bar at the top of every post.
- A related-posts section at the bottom of each post, ranked by shared tags with a recency fallback when too few overlap.
/tagsindex and/tags/[tag]detail pages, statically generated for both locales and added to the sitemap — every tag pill across the site is now a link.
New: error monitoring (Sentry)
@sentry/nextjsinstrumented across all three runtimes — client, server, and edge/middleware — plus aglobal-error.tsxboundary of last resort.- Error capture only: tracing and session replay are off by default, since both add real bundle weight this site's traffic doesn't justify.
- Everything reads one
NEXT_PUBLIC_SENTRY_DSNenv var and no-ops safely when it's unset, so this never breaks a build or local dev before a Sentry project exists. /sentry-example-page— a throwaway test route (excluded from the sitemap and disallowed inrobots.txt) that triggers a client- and server-side error, to confirm the wiring end-to-end.
Also
- Fixed a real bug the Sentry work surfaced along the way: the theme-toggle script set
data-themeon<html>before hydration, and withoutsuppressHydrationWarning, every light-theme visitor was hitting a hydration mismatch that made React discard the server-rendered page and re-render the whole thing client-side on every load. - Regenerated
package-lock.json— it was missing a nested@swc/helpersversion thatnext-intlneeds (distinct from Next.js's own), which passed a plainnpm installlocally but failednpm ciin CI on a clean checkout. - Project card content and screenshots updated to reflect all of the above.
v1.2.0 — SEO: metadata, sitemap, structured data, OG images
Real SEO, previously missing entirely: descriptions, sharing previews, sitemap, and search-engine crawlability.
What's in
- Real per-page metadata — every page now has an actual title and description (individual posts reuse their
linkedin_descriptionteaser), instead of just a bare title. - Open Graph + Twitter cards — links to the site (LinkedIn, anywhere) now render a proper preview card. Posts get a dynamically generated branded image (title + tags); the rest of the site gets a sitewide default.
robots.txt+sitemap.xml— didn't exist before. Sitemap covers every static page, post, and project, in both locales.- Favicon + apple-touch-icon — the site was 404ing on
/favicon.ico. - JSON-LD structured data — Person/WebSite on the homepage, BlogPosting on every post, so Google understands what the content actually is.
- Canonical + hreflang tags on every page for the en/pl locale pair.
- Fixed a real crawl-efficiency bug: the bare apex domain (
michalzawadzki.dev) redirects towww.michalzawadzki.dev, but the sitemap/canonical URLs were pointing at the apex — every single URL Google crawled went through an avoidable extra redirect hop. Fixed to point at the domain that actually serves.
Caveat
This is the technical-infrastructure half of SEO — sitemap, crawlability, correct metadata. It doesn't move rankings or traffic by itself; that's a function of content, backlinks, and domain age over time. Indexing a brand-new domain in Search Console takes days to weeks regardless.
- Real per-page metadata — every page now has an actual title and description (individual posts reuse their
v1.1.0 — LinkedIn descriptions, auto-generated, and a full admin panel
Two additions since v1.0.0: LinkedIn post teasers are now AI-generated automatically, and the site has a real admin panel — create, edit, and delete posts (with images) and moderate comments, all without touching git directly.
New: LinkedIn description automation
generate-linkedin-description.yml— on push to any branch other thanmaintouchingposts/**, fills in a post'slinkedin_descriptionfront-matter field via OpenRouter if it's missing, and commits the result back to that branch. Because it runs on the PR branch itself, the generated teaser shows up as a normal diff to review before merging — edit it by hand if it misses the mark. Also runnable manually (workflow_dispatch) to backfill a branch that predates this workflow.publish-to-linkedin.ymlnow includes that description in the actual LinkedIn post commentary, alongside the title and tags.- Never touches an already-deployed post. The workflow explicitly skips any post slug that already exists on
main, even if that post is missing a description too — a PR branch carries every already-deployed post along with it, not just the new one it's actually about, so this prevents an unrelated stray edit landing on someone else's PR.
New: admin panel (
/admin)- Full CRUD for posts — create, edit (with a live markdown preview matching the site's real typography), delete, image upload — plus comment moderation (delete for spam/abuse cleanup).
- Locked to the site owner only, via a GitHub OAuth login check. That login is identity-only — the actual writes go through a separate, narrowly-scoped GitHub token (Contents: read/write on this repo only) that never reaches the browser.
- Reads and writes go straight against GitHub's API rather than the local filesystem, so the panel always reflects the true state of
main, not whatever happens to be bundled into the currently-running deployment. - Saves commit straight to
main— no PR step. Editing through the panel's own preview is already the review step; that's different from the automated AI-draft pipeline, which still goes through a PR since a human hasn't seen the content yet.
Also
- Collaborate page: fixed the LinkedIn link, dropped the unused Telegram channel, fixed a badge overlap.
v1.0.0
v1.0.0 — First tagged release
A snapshot of the site and its automation as they stand today: a personal portfolio/blog with a self-publishing content pipeline, now with reader comments.
The site
- Frontend: Next.js 15 (App Router) + TypeScript + Tailwind CSS, deployed on Vercel with auto-deploy on push to
main - i18n: English + Polish UI via next-intl (
/en/...,/pl/...), language switcher in the nav - Pages: home, about, projects (with a screenshot gallery — desktop/mobile frames, lightbox), the blog (
/posts), individual post pages, and a collaborate/contact page - Content: blog posts are plain Markdown files with front-matter (
posts/YYYY-MM-DD-slug/index.en.md) — no database, no CMS
New in this release: comments
Post pages now have a comments section, front and center:
- Readers sign in with LinkedIn (Auth.js v5, OpenID Connect) to post a comment — no separate account needed
- Reading comments requires no sign-in; only posting does
- Comments publish immediately (no moderation queue) and persist in Postgres (Neon, via Vercel's Storage integration) — the one part of this site backed by a real database rather than flat files
- Fully optional at runtime: without the LinkedIn app + database env vars configured, the section simply doesn't render, with zero extra client-side network activity anywhere on the site
Automation pipeline
publish-to-linkedin.yml— on push tomaintouchingposts/**, waits for the new post's page to go live, then publishes it to LinkedIn with a link back to the article. Re-runnable manually for a single post without a new commit.generate-draft.yml— runs weekly (and on demand): pulls items from configured RSS feeds (Azure Updates, Azure DevOps Blog, HashiCorp Blog, GitHub Changelog, Simon Willison's blog by default), skips anything already covered by an existing post, drafts a post via OpenRouter (routed to a Claude model) in the site owner's actual voice, gives itself a self-critique/revise pass against a "cut these on sight" AI-writing-tell checklist, then opens a PR with the draft. Nothing publishes without a human merging that PR.ci.yml— typecheck, unit tests, and a production build on every push/PR tomain.- New: a project-local
write-blog-postClaude Code skill — an interactive, conversational alternative to the scheduled RSS pipeline. Talk through a topic in chat, get a draft in the same voice (it re-reads the style guide live so the two paths can't drift apart), review it, then it creates the post file once approved. - Style guide (
automation/draft-instructions.md) now also encodes correct em dash / en dash / hyphen usage — dash misuse is one of the most common AI-writing tells, so both the automated pipeline and the new skill get it right rather than just less frequent.
Testing
Vitest unit tests cover the content layer, the LinkedIn API client, and the draft pipeline's pure helpers — all running in CI on every push/PR.
- Frontend: Next.js 15 (App Router) + TypeScript + Tailwind CSS, deployed on Vercel with auto-deploy on push to