Changelog
Changelog
All notable changes to this project will be documented in this file.
[1.20.0] - 2026-04-26
Added
/searchnow matches podcasts and formats in addition to episodes — typing "Stay" surfaces the Stay Forever podcast itself, "Heftkritik" surfaces the format card, and "Filmumsetzungen" still surfaces the matching episode. Each result is tagged with a colored type badge (Podcast/Format/Folge) and routes to the correct page (/[podcastId],/[podcastId]/[categorySlug], or/[podcastId]/[categorySlug]/[episodeId]).- Type filter chip group above the result list (
Alle / Podcasts / Formate / Folgen) so users can narrow results to a single tier; the existing podcast filter dropdown still composes with it.
Changed
- Search index
search-index.jsonnow emits a unifieditemsarray (12 podcasts + 149 formats + 7,162 episodes ≈ 4.4 MB) with atypediscriminator and a sharednamefield, so a single Fuse instance covers all three tiers. Format and podcast records shiptotalEpisodesplus year ranges so result cards can show "176 Folgen · 2010–2025" without hitting the full dataset. - Result ranking applies a small per-type penalty so a clean podcast or format hit floats above an episode-description fuzzy match at the same Fuse score.
[1.19.1] - 2026-04-26
Fixed
- "Aktiv seit" column on the homepage Podcasts table no longer shows
0for podcasts that contain an episode with an unparseable/empty pubDate (e.g. Spielvertiefung). The analyzer's year-0sentinel for unknown publish dates is now ignored when computing first/last activity year. - Categories that contain no episodes (e.g. Spielvertiefung "Rezension", The Pod "Was tun?", Retrokompott "Echtzeitkompott", and 14 others) no longer appear anywhere in the UI: their per-category pages 404, the per-podcast Episoden Archiv skips the empty
<details>accordions, and the sitemap drops the dead URLs. ThefindCategoryBySlughelper,generateStaticParamsfor/[podcastId]/[categorySlug],EpisodeArchive, andsitemap.tsall share a singlehasEpisodesguard fromlib/categories.ts.
[1.19.0] - 2026-04-26
Added
- Homepage "Folgen pro Podcast" card now uses a real interactive Recharts horizontal bar chart — bars are clickable and route to the matching podcast page, tooltips and axis ticks use German number formatting (
Intl.NumberFormat). - Build-time guard
assertMetadataIdsMatchruns on the homepage render: any future feed name whose webgetId(name)disagrees with the analyzer'smetadata[id](whitespace, casing, diacritics) now fails the build instead of silently breaking nav.
Changed
- Tightened the contract between the web's
Utils.getIdand the Java analyzer'sMain.getId— the web now usesreplaceAll(' ', '')(matching Java byte-for-byte) instead of.replace(/\s/g, ''), with a comment block declaring the contract.
Removed
- Stale planning doc
2026-02-20_replace-homepage-table-with-dashboard-stats.mdat repo root (its decisions shipped in v1.10).
[1.18.0] - 2026-04-26
Added
- Shared
lib/dates.tshelper exposes locale-aware short month labels viaIntl.DateTimeFormatso chart axes pick up future locale changes automatically.
Changed
- All remaining English UI strings translated to German — dashboard stat cards (
Podcasts gesamt,Folgen gesamt,Ø Folgen / Podcast,Formate gesamt,Aktivster Podcast,Zuletzt aktualisiert,Folgen pro Podcast), error and 404 pages, breadcrumbs (Übersichtinstead ofDashboard), Paywall/Public badges, download button tooltips, page metadata (<title>, descriptions), and the sortable-table column header. - Number, date, and relative-time output now goes through
Intl.NumberFormat/Intl.DateTimeFormat/Intl.RelativeTimeFormatinstead of ad-hoctoFixed/toLocaleString/ hand-rolled "vor X T." strings — episode file sizes render as6,22 MB(German thousands/decimal), last-published cells showvor 3 Tagen-style relative dates, and chart month labels come from the locale rather than hardcoded arrays. /changelogsimplified to a single section that renders the canonical projectCHANGELOG.mdonly — the separate Web/Backend tabs and the abandonedweb/CHANGELOG.mdstub were removed.
[1.17.0] - 2026-04-26
Added
- Per-category pages now render the same chart suite as per-podcast pages — episodes per year, average episodes per month, cumulative growth, year × month activity heatmap, and duration histogram — scoped to the category's episodes.
- Header navigation is now two-tiered: 🔍 Suche and the three primary podcasts (Mindestens 10 Zeichen, Stay Forever, The Pod) sit above a thin divider; all other podcasts appear below in a quieter secondary tier so the eye lands on the headline feeds first.
Changed
- Regenerated analyzer output against the v1.14.1 config so Stay Forever no longer surfaces stale
10x10,Flashback, orSonderfolgecategories on the rendered site (the orphan "10x10: Filmumsetzungen" episode now falls into_ Ohne Kategorieas intended).
[1.16.0] - 2026-04-26
Added
- Per-category index pages at
/[podcastId]/[categorySlug]— one static page per(podcast, format)pair, listing every episode in that format grouped by year. Sitemap and/llms.txtenumerate them. - Reusable
<Breadcrumb>component, now rendered on the per-podcast, per-category, and per-episode pages so visitors can navigate the Dashboard → Podcast → Format → Episode hierarchy.
Changed
- Episode permalinks moved from
/[podcastId]/[episodeId]to/[podcastId]/[categorySlug]/[episodeId](e.g./spieleveteranen/regulare-folge/10f7q1z). Old 2-segment URLs are no longer generated and now return 404. Search results, sitemap entries, and the in-card "Podcast Format" link all use the new shape. The build asserts that no two formats within a podcast slugify to the same value. - The
_ Ohne Kategoriebucket routes under the literal slugohne-kategorie.
[1.15.0] - 2026-04-26
Added
- New
/searchroute: type-ahead full-text search over episode titles and descriptions across every analyzed podcast, powered by fuse.js against a slim/search-index.json(~3.4 MB / ~870 KB gzipped) generated at build time. Results can be filtered by podcast. - Per-episode permalink pages at
/[podcastId]/[episodeId]— one static page per episode (~7,000 in total) with the sanitized description, an inline<audio>player, MP3 download, duration/size badges, breadcrumb navigation, and OpenGraph metadata. Episode IDs are stable FNV-1a hashes of the source URL (ortitle@pubDatefallback); the build asserts uniqueness. - Episode titles in the per-podcast archive now link to their permalink page.
- Sitemap and
/llms.txtenumerate every per-episode URL and the new/searchpage. - Homepage "Podcasts Overview" gained filters and sortable columns: filter by name, toggle "only podcasts that released a new episode in the last 12 months", and sort by episodes / first year / last-episode date.
Changed
- Header navigation marks the current podcast or
/searchlink as active (aria-current="page"plus a Bootstrap.activeclass). SortableTablecolumn headers are real<button>elements witharia-sortinstead of clickable<th>cells, so they are keyboard-reachable and announced correctly by screen readers.- Episode-info modal now closes on Escape and on backdrop click, gets a real
.modal-backdrop, traps body scroll while open, and exposesaria-modal/aria-labelledby. - Global
:focus-visibleoutline so keyboard focus is visible across the lighter pill nav and table headers.
Fixed
- Homepage "Podcasts Overview" previously built each podcast link by sanitizing the display name (
name.replace(/\s/g, '').toLowerCase()) and looking it up inmetadata— broken for any podcast whose ID did not happen to match its squashed name. Replaced with a new clientPodcastsTablethat uses realmetadataIDs, so every row links to the correct podcast page.
[1.14.1] - 2026-04-26
Changed
- Stay Forever feed: removed the
10x10,Flashback, andSonderfolgecategory mappings. These captured only one or two episodes each and now fall through to_ Ohne Kategorie.
[1.14.0] - 2026-04-26
Added
- Per-podcast page: each "Podcast Format" row in the sortable table is now an anchor link that jumps to the matching section in the episode archive below and auto-expands it. Hash deep links (e.g.
#archiv-talk) work the same way — the targeted<details>opens and scrolls into view on load and onhashchange.
[1.13.0] - 2026-04-26
Changed
- Episode-to-category matcher rewritten to use title-only, boundary-aware prefix matching. A pattern only hits when it sits at a word/non-word boundary on both sides of the (lowercased) title, longer patterns win over shorter ones at the same boundary, and description-based TF-IDF semantic matching is removed entirely. The old semantic fallback was the source of false positives where any episode whose description mentioned common German words like "Spiel" or "Geschichte" leaked into the "Ein Spiel und seine Geschichte" category.
Added
- New Stay Forever categories:
Hommage,Filmpodcast,Down to the Detail,10x10,Sonderfolge,Flashback. Episodes that previously fell through to Unclassified or were misrouted into "Ein Spiel und seine Geschichte" now land in their proper buckets.
Fixed
- Episodes like
Sonderfolge: Die Faszination des Sammelns,Filmpodcast: Bloodrayne,Hommage #1: Faith, andDown to the Detail: Gabriel Knight 2 #1no longer appear under "Ein Spiel und seine Geschichte" — that category now contains only the four realEin Spiel und seine Geschichte: …episodes.
[1.12.1] - 2026-04-24
Changed
/changelogpage now shows the Web section first and the Backend section second, with a custom segmented-pill nav replacing the Bootstrap default, a proper "Changelog" title, and scroll-margin offsets so anchor jumps land cleanly below the sticky navbar.
[1.12.0] - 2026-04-24
Added
- New
/changelogroute that renders both the project and the web-specificCHANGELOG.mdfiles side-by-side at build time, with jump-nav pills between sections. Accessible by clicking the version number in the footer.
Changed
- Bundle-size / load optimizations: swapped the full Bootstrap CSS import (~225 KB) for a selective SCSS bundle (−65 KB gzipped), DOMPurify is now dynamic-imported inside the episode info modal opener instead of shipped with the initial payload, and all five per-podcast charts are wrapped in a new
<LazyMount>IntersectionObserver component so their recharts chunks only download as they scroll into view. - Silenced Bootstrap 5.3's internal Sass deprecations (
import,global-builtin,color-functions,if-function,legacy-js-api) viasassOptions.silenceDeprecationsinnext.config.ts.
[1.11.0] - 2026-04-24
Added
- "Episoden Archiv 📁" button in the per-podcast header that jumps to the archive section via a
#archivanchor.
Changed
- All internal and external link wrappers in the podcast stats table and per-podcast header now use
next/linkinstead of plain<a href>— dashboard navigation is client-side routed with automatic prefetching, no more full-page reloads. - Per-podcast header now keeps the title on the left and the "Episoden Archiv" / "Homepage" buttons grouped together on the right, instead of spreading all three across the row.
[1.10.0] - 2026-04-24
Added
- Three new charts on every podcast detail page: a cumulative episode count over time (area chart), an activity heatmap showing episodes per calendar month per year, and a histogram of episode length in 30-minute buckets. The heatmap and duration histogram are hidden for feeds that lack the underlying data.
- Anchor id on the episode archive heading so it can be deep-linked.
Changed
- Consolidated the per-podcast chart aggregation into a single deduped episode walk (dedupe by url, fallback title@pubDate). All four charts derive from the same pass, keeping year/month computation consistent under Europe/Berlin.
Removed
- Unused
TableHeadlinecomponent.
[1.9.0] - 2026-04-24
Changed
- All routes are now prerendered at build time. The per-podcast detail page uses
generateStaticParams()to emit one static HTML file per podcast (withdynamicParams = falseso unknown ids 404 at build), and/llms.txtis markedforce-staticso its response is baked in rather than evaluated per request. No more on-demand server rendering.
Fixed
- Navigation items are now vertically centered on desktop. The featured pills no longer push plain links off-axis because the
.navbar-navflex container centers on the cross-axis and each.nav-linkflex-centers its inner<li>. Mobile offcanvas layout is unchanged.
[1.8.0] - 2026-04-24
Added
- Second chart on each podcast detail page showing the average number of episodes released in each calendar month (Jan–Dez), averaged across the podcast's active years. Complements the existing per-year view and reveals release cadence / seasonality.
- Explanatory headlines above both per-podcast charts (
Folgen & Formate pro JahrandØ Folgen pro Monatwith an active-years subtitle).
Changed
- Episode-archive avatars now load directly from their source CDNs instead of funnelling through
/_next/image, so the browser fans out requests across many origins in parallel instead of queueing them through a single endpoint. WordPress/Jetpack-hosted covers (~87% of the catalogue) additionally get a?w=64&h=64edge resize, dropping per-avatar payloads from 100s of KB to 2–4 KB. - Each
<details>section in the episode archive now lazy-mounts its list on first open, so collapsed sections no longer hydrate hundreds of components up front.
[1.7.0] - 2026-04-24
Added
- Per-feed
enabledflag inconfig.yaml: setenabled: falseon any feed to skip it entirely (no fetch, no parse, no categorization, and it disappears from the frontend). Omitting the flag defaults toenabled: true, so existing configs are unaffected. - Navigation can now highlight selected podcasts via the
FEATURED_PODCAST_IDSset in the header, rendered with dedicatednav-link-featured/nav-item-featuredclasses as a warm amber pill.
Changed
- Disabled the
Insert Moin,Kapitel Eins, andLast Game Standingfeeds in the shipped config. They no longer appear in the exported results. Mindestens 10 Zeichen,Stay Forever, andThe Podare now visually highlighted in the navigation.
[1.6.1] - 2026-04-24
Changed
- Weekly GitHub Actions run now persists the analyzer feed cache between runs via
actions/cache@v5, so conditional GETs and body-SHA short-circuits actually fire on the cron - Weekly run logs
feed_cache_stats.jsonto the job log after the analyzer finishes for at-a-glance cache visibility
[1.6.0] - 2026-04-24
Added
- RSS feed HTTP cache (Layer 1): analyzer now issues conditional
If-None-Match/If-Modified-SinceGETs, reuses cached XML bodies on 304, and falls back to SHA-256 body comparison on 200 responses. Stale cache is served on HTTP failures so weekly CI runs are resilient to transient 5xx errors. Honours-DrssAnalyzer.forceRefresh=true/RSS_ANALYZER_FORCE_REFRESH=truefor forced re-downloads. - Parsed-items cache (Layer 2): keyed by the feed body SHA-256, skips XML parsing on warm re-runs when the body bytes haven't changed. Raw
ParsedItemis now a denormalized JSON-serializable record so it can be rehydrated from disk without round-tripping through the rssreader library. - Categorization-result cache (Layer 3): keyed by matcher config hash + aggregated per-sub-feed SHAs; when both match, the entire
match / count / countYear / countYearTitlepipeline is skipped for that feed and the cached result fragment is spliced straight into the finalResult. - Per-layer cache observability: per-feed INFO log lines (304 / body-sha match / fresh / stale-on-failure / parsed-items hit / category-results hit), one-line run summaries for each cache layer, and a machine-readable
feed_cache_stats.jsonwritten on every run. - Namespaced cache store:
FileSystemCachenow supports multiple independent namespaces plus side-file payloads, with transparent migration of legacy flatcache.yamlfiles into a default namespace.
Changed
- Measured impact (29 feeds, today's config): cold run 45.9 s, warm re-run 25.1 s (-45 %). 27/30 feeds served from the HTTP cache, 27/27 parsed-items cache hits, 13/13 category-results cache hits; downloaded bytes drop from 41.2 MB to 12.9 MB on warm runs. The remaining runtime is dominated by the export phase (~22 s) which is addressable in a follow-up.
[1.5.0] - 2026-04-24
Added
- Footer now displays the current app version
Changed
- Bumped
nextto^16.2.4anddompurifyto^3.4.1
[1.4.0] - 2026-04-24
Added
- Episode info modal now shows the release date and time above the description
- "Was tun?" section on config values in
config.yaml
Fixed
- Episodes published late in the day now bucket into the correct year based on the publisher-local date (Europe/Berlin), preventing year-boundary leakage
[1.3.0] - 2026-04-20
Added
- Enabled Umami session replay by default via
recorder.jswithdata-sample-rate,data-mask-level, anddata-max-durationattributes - Session replay tunables via
NEXT_PUBLIC_UMAMI_SAMPLE_RATE,NEXT_PUBLIC_UMAMI_MASK_LEVEL, andNEXT_PUBLIC_UMAMI_MAX_DURATION
Changed
- Migrated Umami script host to
umami.lucanerlich.com
[1.2.3] - 2026-04-13
Added
- Mindestens 10 Zeichen: added "Gaming Stories" category
[1.2.2] - 2026-04-13
Fixed
- GitHub Actions workflow now also updates
web/public/results.jsonso Nixpacks/Coolify builds pick up fresh podcast data
[1.2.1] - 2026-04-13
Changed
- GitHub Actions workflow now only commits
result.json(the only file the frontend reads) instead of all output files - Workflow skips commit when podcast data is unchanged, ignoring the
createdAttimestamp that varies between runs
[1.2.0] - 2026-04-13
Added
- Weekly GitHub Actions workflow to automatically refresh RSS data (runs Sunday-to-Monday night)
- Gradle
applicationplugin so the analyzer can be run via./gradlew run
Fixed
- Opted into Node.js 24 for GitHub Actions runners to resolve deprecation warnings
[1.1.1] - 2026-04-13
Changed
- Improved episode categorization across 6 podcasts by adding missing keyword variants to config.yaml
- Spielvertiefung: added "Über" keyword to catch ~20 deep-dive episodes, "Jahresrückblick" category, and "In Gespräch" typo variant
- Stay Forever: added "10 Jahre Klüger"/"10 Jahre klüger" to match Patreon episode naming
- Down to the Detail: added "Ambience" variant for Ambients category
- Insert Moin: added "gamescom", "E3" to Event Recap, and new "Feriencamp" category
- HookedFM: added "gamescom", "Jahresrückblick" to Special, and new "Hooked on" category
- Mindestens 10 Zeichen: added "Spezial:" category
[1.1.0] - 2026-04-06
Added
- Initial release with DownloadGoodies script for automated file downloads