mz.status

Changelog

7 releases
  1. v1.6.0

    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_TOKEN health before it expires silently.

    Fixed: per-post OG image and meta description

    Next 16 passes params as 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-written linkedin_description frontmatter field.

    Fixed: homepage accessibility (WCAG AA)

    Follow-up from an /impeccable critique pass — 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"
  2. 1.5.0

    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 verify job 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-version to 24 across every workflow, and actions/checkout, actions/setup-node, actions/upload-artifact, and peter-evans/create-pull-request to their current majors (all now default to Node 24).

    Also fixed the one cleanly-fixable npm audit finding: next's sharp dependency was pinned below a version with known libvips CVEs, patched via a package.json overrides pin without touching Next's own version.

    Upgraded: Next.js 15 → 16

    • middleware.tsproxy.ts (Next 16's rename of the special file — the actual next-intl middleware logic didn't change).
    • Turbopack is now the default bundler in both next dev and next build.
    • tsconfig.json's jsx setting moved to react-jsx (now mandatory — Next 16 uses the React automatic runtime).
    • The new cacheComponents opt-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/node bumped to match the Node 24 target.
  3. v1.4.0

    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:image card (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.tsx mirrors the post route, so a shared project link now shows its own title and tags.

    New: Lighthouse CI budget

    A lighthouse job now runs after every verify pass, checking the homepage, posts index, projects index, and tags index with treosh/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 same remark pipeline 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.
  4. v1.3.0

    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 suppressHydrationWarning on <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.typography in tailwind.config.ts) instead of a hardcoded prose-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.
    • /tags index 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/nextjs instrumented across all three runtimes — client, server, and edge/middleware — plus a global-error.tsx boundary 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_DSN env 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 in robots.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-theme on <html> before hydration, and without suppressHydrationWarning, 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/helpers version that next-intl needs (distinct from Next.js's own), which passed a plain npm install locally but failed npm ci in CI on a clean checkout.
    • Project card content and screenshots updated to reflect all of the above.
  5. v1.2.0

    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_description teaser), 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 to www.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.

  6. v1.1.0

    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 than main touching posts/**, fills in a post's linkedin_description front-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.yml now 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.
  7. v1.0.0

    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 to main touching posts/**, 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 to main.
    • New: a project-local write-blog-post Claude 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.