Changelog
All notable changes to this project will be documented in this file.
[1.7.2] - 2026-05-29
Added
- Expanded the Vitest suite to cover the RSS feed pipeline (XML escaping, Strapi media normalization, Markdown→HTML sanitization/XSS, article feed, feed-route helpers), Strapi query builders, metadata helpers (excerpt, keywords, Open Graph image), Markdown preprocessing and heading extraction, JSON-LD generators, analytics event IDs, image URL/hostname allow-listing, client-IP parsing, and relative date formatting. The suite now runs 436 tests across 38 files as part of
pnpm run build. - Cross-package contract test pinning the cache-invalidation target list to the Strapi backend’s, so the frontend taxonomy and backend targets can no longer drift silently.
[1.7.1] - 2026-05-28
Added
- The on-site audio player on podcast detail pages now routes through the same download-tracking endpoint as the RSS feed, so plays started on the website are recorded as
podcast-downloadUmami events too (whenFEED_AUDIO_TRACKING_ENABLEDis enabled).
Changed
- Podcast download counting now ignores seek/continuation range requests so a single play or download counts once; with tracking enabled the on-site player uses
preload="none"so the event fires when playback starts rather than on every page load.
[1.7.0] - 2026-05-28
Added
- Podcast RSS download tracking: when enabled, each episode’s
<enclosure>URL points at an on-domain endpoint that records a custom Umamipodcast-downloadevent (episode slug and title) before redirecting to the audio file, so downloads initiated by podcatcher apps can finally be measured. Off by default; toggle with theFEED_AUDIO_TRACKING_ENABLEDenvironment variable. Episode GUIDs stay identical whether tracking is on or off, so existing subscribers are unaffected.
Changed
- Build/dependency tooling: pinned pnpm to v10 and removed redundant
ignore-scriptssettings from.npmrc.
[1.6.0] - 2026-05-16
Added
- Per-game detail pages at
/m12g/spiele/[slug]: each nominated game now has its own shareable page with stats (nominations, wins, total votes) and a per-month timeline showing votes earned and which months it won. All 56 pages are prerendered at build time; unknown slugs return 404. - Streaks card on
/m12g: highlights the longest consecutive-month nomination streak and the longest consecutive-month win streak, with deep links to the streak holder’s detail page. - Open Graph image for
/m12g: dynamic share card showing the all-time top-3 leaderboard.
Changed
- Game names in the leaderboard and game index now link to the internal detail pages; the external store link is preserved as a small
↗affordance next to each name.
[1.5.7] - 2026-05-16
Added
- Vitest test suite for pure utility modules — m12g pipeline (parser, game-history aggregate, stats projections, title-defender derivation), formatters, validators, security helpers (slug validation, secret comparison, rate limit). 18 test files / 215 tests, runs in ~150ms.
pnpm test(watch) andpnpm test:run(one-shot) scripts.
Changed
pnpm run buildnow runsvitest runbeforenext build, so failing tests block the build and regressions in pure-logic modules are caught at build time.
Fixed
formatMonthCompactno longer throwsRangeErroron invalid month-id input; falls back to the input string, matchingformatMonthLongandformatMonthShortbehaviour.
[1.5.6] - 2026-05-16
Changed
- Refactored the M12G data pipeline: markdown parsing is split from filesystem loading, a new Game-history aggregate powers both the leaderboard and the alphabetical game index, and title-defender derivation is now a pure projection (no in-place mutation). No behaviour changes for valid month files.
Fixed
- M12G month files with missing required frontmatter (
forum,title,finalized) or malformed list items now fail loud with the file name in the error, instead of silently dropping the month from the overview.
[1.5.5] - 2026-05-16
Changed
- Added
.npmrcwith supply-chain security settings: 7-day package quarantine, blocked exotic subdependency specifiers, and disabled lifecycle scripts - Updated April 2026 m12g list content
[1.5.4] - 2026-05-11
Fixed
- Docker production build no longer fails on
/changelogprerender withENOENT: /app/public/changelog.md: the root andfrontend/.dockerignorefiles excluded all*.mdfrom the build context, soCHANGELOG.mdwas missing inside the container andscripts/copy-changelog.mjshad nothing to copy; both files now whitelistCHANGELOG.mdwith!CHANGELOG.md, and the script exits with a clear error (instead of warning and continuing) when the source file is missing, so this kind of misconfiguration fails fast and visibly
[1.5.3] - 2026-05-11
Changed
- Simplified the
/changelogimplementation: a newscripts/copy-changelog.mjsprebuild step copiesCHANGELOG.mdtopublic/changelog.md, and the page reads it directly viafs.readFileSync; replaces the previous generated TypeScript module approach. As a bonus, the raw markdown is now also available at/changelog.md
Fixed
- Silenced a pre-existing Turbopack “Encountered unexpected file in NFT list” build warning originating from
src/lib/rss/feedCache.tsruntime cache writes; the documented/*turbopackIgnore: true*/magic comment is a no-op forfs.*/path.join(it only applies to dynamic imports/requires), so the warning is suppressed via the newturbopack.ignoreIssueconfig innext.config.ts
[1.5.2] - 2026-05-11
Fixed
/changelogpage now shows content: replaced the fragile build-timeprocess.cwd()+ env-var approach with ascripts/generate-changelog.mjsprebuild script that usesimport.meta.dirname(always relative to the script file, never to the working directory) to readCHANGELOG.mdand write its content intosrc/generated/changelog-content.ts; the page imports this TypeScript constant directly, so the content is resolved at compile time regardless of where the CI/CD runner executes from
[1.5.1] - 2026-05-11
Fixed
/changelogpage now builds reliably in all CI/CD environments:CHANGELOG.mdis read innext.config.tswhereprocess.cwd()is guaranteed to be the project root, then injected as a build-time environment variable (BUILD_CHANGELOG_CONTENT) — eliminating runtime filesystem access and the path ambiguity that broke cloud builds
[1.5.0] - 2026-05-10
Added
- Version number in the site footer (next to the font and theme pickers) linking to a new
/changelogpage that renders this CHANGELOG; the footer label and the page’s metadata both read frompackage.jsonso future releases update both automatically
Fixed
/changelogloadsCHANGELOG.mdreliably in CI and cloud deploys:getChangelogMarkdown()checks both the app root andfrontend/CHANGELOG.mdwhenprocess.cwd()is the monorepo root, andoutputFileTracingIncludesinnext.config.tsforces the file into the traced server output so Turbopack/serverless bundles no longer omit it
[1.4.0] - 2026-05-10
Added
- Per-page Twitter Card metadata on all static and list routes (
/,/artikel,/podcasts,/kategorien,/team,/datenschutz,/impressum,/feeds,/m12g,/m12g/spiele,/ueber-uns); previously these routes inherited the root default and rendered with the wrong title in social shares articleSection,keywords,wordCount, andinLanguagefields onBlogPostingJSON-LD;keywords,inLanguage, and apartOfSeries.imageonPodcastEpisodeJSON-LDinLanguageandisPartOf(WebSite reference) onCollectionPage(category) andProfilePage(author) JSON-LD;worksFor(Organization reference) on authorPersonJSON-LD- Description fallback for article and podcast detail pages: when the CMS
descriptionis empty, a 155-character excerpt is now derived from the article body or podcast shownotes for both the<meta name="description">tag and the JSON-LD, eliminating duplicate meta descriptions across articles - Alt text on the static
/images/m10z.jpgOpen Graph image for every static and list route
Changed
- Consolidated the eleven near-identical static-page metadata blocks behind a single
buildStaticListMetadata()helper that emits the standard OpenGraph, Twitter Card, canonical URL, and alt-tagged share image in one call
[1.3.4] - 2026-05-10
Changed
- Centralised image URL handling behind a single
lib/imagemodule: the hostname allowlist, Strapi-relative URL resolution, and search-index URL normalisation now live in one place;SearchModal’s duplicated normaliser was collapsed onto the shared seam - Split the Markdown component’s plugin pipeline and rehype-sanitize allowlist out into
lib/markdown/plugins.tsand the custom inline-syntax pre-processor (<mark>mark</mark>,<ins>ins</ins>,<sup>sup</sup>,<sub>sub</sub>) intolib/markdown/preprocess.tsso the security-relevant allowlist is auditable in one file - Extracted the article and audio feed handlers’ shared paginated-fetch and feed-single-config logic into
lib/rss/feedFetcher.ts; both handlers slimmed (article 140 → 90 lines, audio 360 → 311 lines) while preserving their content-specific concerns intact - Replaced the 16 inline
qs.stringifycall sites inlib/strapiContent.tswith three intent-shaped query builders (buildBySlugQuery,buildBySlugsQuery,buildListQuery) and named populate presets inlib/strapi-queries; the file shrank from 1,214 to 787 lines and each fetcher is now 5–15 lines of intent - Tightened the cache-invalidation taxonomy: the frontend
InvalidationTargettype now derives fromINVALIDATION_TAXONOMYkeys, the backend collapses its separate publish/update target maps into one declarativeUID_TO_TARGETSconfig, and a documented cross-wire contract is now present in both files - Added a
pnpm run snapshot:feedsscript that locks the public/rss.xmland/audiofeed.xmlXML byte-output to a golden fixture; runs after refactors as a regression guard - Upgraded backend dependencies (Strapi-side) and minor frontend dependency bumps
- Updated team page description copy
[1.3.3] - 2026-05-06
Fixed
- RSS and audio feed requests no longer block on synchronous Strapi rebuilds — the request path always serves the cached XML from disk or memory; the background scheduler is the sole refresh path under normal operation. Prevents the request-pile-up failure mode that previously crashed the site under load.
Changed
- Consolidated the article and audio feed route handlers into a shared
feedCachemodule that owns disk I/O, scheduling, ETag/304, rate limiting, and fallback responses; the per-feed handlers now only supply Strapi fetch and XML generation specifics
[1.3.2] - 2026-05-06
Added
- April 2026 M12G dataset draft
Changed
- Upgraded Next.js (16.2.3 → 16.2.4), undici, dompurify, and @fancyapps/ui
- Cache invalidation endpoints now accept either
FEED_INVALIDATION_TOKENorLEGAL_INVALIDATION_TOKENfor any target - Consolidated ten
/api/<type>/invalidateroute handlers into a single dynamic/api/[target]/invalidateroute backed by a declarative invalidation taxonomy
[1.3.1] - 2026-04-20
Added
- Status page link (status.m10z.de) in the footer’s Rechtliches section
[1.3.0] - 2026-04-20
Added
- Enabled Umami session replay by default via
recorder.jswithdata-sample-rate,data-mask-level, anddata-max-durationattributes - Support overriding the Umami base URL via
NEXT_PUBLIC_UMAMI_URL; session replay tunables viaNEXT_PUBLIC_UMAMI_SAMPLE_RATE,NEXT_PUBLIC_UMAMI_MASK_LEVEL, andNEXT_PUBLIC_UMAMI_MAX_DURATION
[1.2.3] - 2026-04-16
Fixed
- Participation chart now shows newest months first, consistent with the Hall of Fame ordering
[1.2.2] - 2026-04-16
Added
- Early Access badge on M12G game cards, replacing the verbose “(Early Access)” suffix in game titles with a compact “EA” pill
[1.2.1] - 2026-04-16
Added
- March 2026 M12G dataset (“Early-Access-Armee vs. Full-Release-Rudel”)
Fixed
- Titelträger badge only appearing for one game when multiple previous winners were nominated again
[1.2.0] - 2026-04-13
Added
/llms.txtroute following the llmstxt.org proposal for LLM-friendly site discovery
Changed
- Updated undici dependency (7.24.7 → 7.24.8)
[1.1.2] - 2026-04-11
Changed
- Added technical inline comments across 26 files in frontend and backend to improve readability and maintainability
- Documented non-obvious patterns: retry/backoff strategies, Strapi response normalization, atomic writes, timing-safe comparison, feed deduplication, focus trapping, and cache invalidation hierarchies
[1.1.1] - 2026-04-11
Fixed
- Feed rebuild race condition where concurrent disk writes could corrupt cached XML
- Article feed
lastBuildDatechanging on every rebuild, defeating ETag-based 304 responses - New content taking up to 30 minutes to appear in feeds after publishing (now rebuilds within 10 seconds)
- In-memory feed cache bypassed on every request, causing unnecessary disk I/O
- Invalid
<lastBuildDate>element inside<item>tags in audio feed (RSS 2.0 spec violation) - Missing XML declaration in generated article and audio feeds
- ETag comparison not handling multi-value
If-None-Matchheaders per RFC 7232 - Fallback feed XML not escaping interpolated values
- Rate limit cleanup timer preventing clean process exit
Changed
- Feed disk writes now use atomic rename to prevent partial-file corruption on crash
- Feed invalidation uses debounced rebuild to prevent server overload during batch publishing
[1.1.0] - 2026-04-11
Added
- Related content sections on article and podcast detail pages
- Searchable game index page at /m12g/spiele
- Static pages included in search index
- Open Graph image generation for improved social sharing
- Scheduled publishing support for prepared entries
- SQL injection protection on user-facing inputs
- Retry logic for transient Strapi fetch failures (timeouts, connection resets)
Fixed
- Cache invalidation now covers related-content, article-feed, and audio-feed tags
- Sitemap invalidation endpoint returning incorrect response body
- Secret verification no longer leaks token length through timing differences
- Improved error handling and logging in cache invalidation process
Changed
- Updated dependencies (Next.js 16.2.3, React 19.2.5, undici 7.24.7)
- Refactored content structure to use root attributes instead of BaseContent component
- Removed Beta badge from M12G header