Changelog
Changelog
All notable changes to this project will be documented in this file.
[2.9.0] - 2026-05-02
Removed
- OPML feed export: the
/feeds.opmlendpoint and its sidebar button have been removed.
[2.8.1] - 2026-04-30
Fixed
- Removed redundant note about duplicate counting from supporter stats display.
[2.8.0] - 2026-04-30
Added
- Revenue comparison chart on the compare page, showing estimated monthly Patreon and Steady earnings per podcast grouped by currency.
Fixed
- Supporter history chart now renders with a single data point instead of requiring at least two.
Changed
- Removed per-platform patron breakdown from the supporter overview table for a cleaner display.
[2.7.0] - 2026-04-30
Added
- Auto-gather Patreon & Steady supporter stats for podcasts (#28).
- Show KPI cards as 2×2 grid on mobile (#27).
[2.6.1] - 2026-04-30
Removed
- Playwright and the entire E2E test suite (
web/e2e/,web/playwright.config.ts,web/playwright-report/,web/test-results/, thee2e.ymlGitHub Actions workflow, and the@playwright/testdevDependency). Vitest unit tests remain unchanged. - Badges on the homepage cards: the four KPI cards (Podcasts gesamt, Folgen gesamt, Ø Folgen / Podcast, Formate gesamt) lost their delta/info badges, and the "Aktivster Podcast" and "Zuletzt aktualisiert" cards lost their pill badges. The episode count for the most active podcast is still shown inline next to the name.
[2.6.0] - 2026-04-30
Added
- Each podcast detail page now shows the homepage favicon next to the podcast title. The icon is derived at render time from the existing
metadata.homepageURL via Google's favicon service (s2/favicons,sz=64), with graceful fallback when the site has no favicon and silent hiding on load errors.
Changed
- Bumped
lucide-reactfrom^1.12.0to^1.14.0.
[2.5.0] - 2026-04-30
Added
- New
noPaywallAccessflag on each podcast in the backendconfig.yaml. When set, the podcast detail page shows an info banner ("Eingeschränkte Auswertung") letting visitors know that the statistics are based only on publicly available episodes because we have no access to the paid feed. Initially set for OKCool, Spieleveteranen, Mindestens 10 Zeichen, and HookedFM.
[2.4.0] - 2026-04-30
Added
- Custom equalizer brand mark (RSS dot + three ascending bars) now serves as the favicon, sidebar logo, and header home button. Replaces the default Next.js favicon and the lucide
Rss/Homeicons. Adds anicon.svgwithprefers-color-schemelight/dark variants and a 180x180apple-iconroute for iOS home-screen.
Fixed
- Row selection checkboxes in the "Podcasts Übersicht" table now visually toggle when clicked. The React Compiler memoized
DataTablebased on the stabletablereference fromuseReactTable, so the body did not re-render when onlyrowSelectionchanged; opted the file out with'use no memo'.
Changed
- Re-enabled the
insertmoinpodcast feed in the scraper config; regenerated reports, results, and cached outputs accordingly.
[2.3.0] - 2026-04-30
Added
- Comprehensive Playwright E2E test suite extended from 3 tests to 71 tests across 11 files, covering home, search, podcast detail, compare, changelog, category, and episode routes, plus tab persistence, theme toggle, Cmd+K palette navigation, and RSS/OPML feeds. (#25)
- Expanded schema.org JSON-LD across the full site:
@graphon the home page withWebSite+SearchAction, stable@idanchors,PodcastSerieswith@idand cover image on podcast pages,CollectionPagewithisPartOfon category pages,BreadcrumbListand alignedpartOfSerieson episode pages, andWebPage+SearchActionon the search and changelog pages.SearchClientnow reads an optional?q=on load so the URL template matches real behavior. (#24)
Changed
- Search page URL parameter aligned to
?q=(was?query=) so JSON-LDSearchActionURL template and actual client routing agree. (#24) - Home, search, changelog, podcast, category, and not-found pages now carry explicit
alternates.canonicaland fullopenGraphmetadata. Not-found pages additionally setrobots: noindex. (#23) - Podcast and category page main titles use a semantic
<h1>instead of the formerCardTitle(div); podcast chart section headings promoted fromh3toh2. Compare page main card title is an<h1>. (#23) - Sitemap:
/changelogadded with weekly change frequency.llms.txtupdated to reference changelog. (#23)
Fixed
- Existing Playwright tab-persistence test corrected to match the current
'Episoden'tab label. (#25)
Tests
- Extensive JUnit 5 + AssertJ + Mockito unit tests added for the
java/rss-analyzermodule: cache namespace/YAML/migration,FeedFingerprint,Config.Feed,ParsedItemnormalization and mapping, domain records,JsonTransformer,RssExporterpipeline,SmartCategoryMatcher, andCategoryResultsCache. (#22)
[2.2.9] - 2026-04-29
Fixed
- Next.js production build failed TypeScript check when both
utils.test.tsandUtils.test.tsexisted (Linux-only casing clash). Renamed thecn()Vitest file tocn.test.tsso it no longer collides withUtils.test.ts(@/components/Utils).
[2.2.8] - 2026-04-29
Fixed
- "Folgen & Formate pro Jahr" chart no longer shows negative Y-axis values. The linear regression trend line could produce negative fitted values for early years (when the intercept is below zero), causing Recharts to auto-scale the axis floor into negative territory (e.g. down to -55). Fixed by adding
domain={[0, 'auto']}to allYAxisinstances inYearSumChartandCategoryYearChart, clamping the axis minimum to zero.
[2.2.7] - 2026-04-29
Added
- "Zum Format" outline button in each Episoden-Archiv accordion header — one click to the dedicated category page from the collapsed state. Right-aligned, icon-only on small screens.
[2.2.6] - 2026-04-29
Added
- Persistent Home / Übersicht button in the top header bar — always visible regardless of sidebar state, highlights when active, icon-only on small screens.
Fixed
- Mobile sidebar now closes automatically after tapping a nav link. The shadcn Sidebar Sheet was previously left open after navigation, requiring a second tap to dismiss.
[2.2.5] - 2026-04-29
Changed
- Chart palette reshuffled so adjacent series contrast:
--chart-2is now teal (was amber) and--chart-3is violet (was yellow). The "Folgen & Formate pro Jahr" chart now reads as orange × teal instead of orange × amber, and the homepage distribution //compareoverlay chart cycles warm/cool/warm/cool for clearer separation.
[2.2.4] - 2026-04-28
Fixed
- Tests no longer fail in sandboxed CI/Coolify builds with
Error: No such built-in module: node:. Thelib/trends.tsmodule was split into a pure-math half (safe to import from anywhere, no Node built-ins) and a server-onlytrends-server.tshalf that still loadsresult-history.jsonlviafs/path(without thenode:prefix that some sandboxes choke on).
[2.2.3] - 2026-04-28
Changed
- Empty / zero / null cell values across every table (homepage Podcasts overview, per-podcast Format-by-Year matrix, comparison metrics) now render as blank space instead of an em-dash placeholder, so the eye can scan rows for actual data without the dashes adding noise. Year ranges, duration buckets, and the year-group divider in the category episode list are unchanged — those dashes represent real ranges, not missing data.
[2.2.2] - 2026-04-28
Changed
- Homepage Verlauf sparkline now excludes the current (in-progress) year so the trend isn't artificially pulled downward. The column header carries an asterisk and the cell tooltip explains "Ohne laufendes Jahr".
[2.2.1] - 2026-04-28
Changed
- Homepage KPI cards trimmed to label, value, and delta badge. Removed the static footer copy ("Inhalte über alle Podcasts", "Verteilung der Produktion", "Über alle Podcasts gemittelt", etc.) — the badge already conveys the trend.
[2.2.0] - 2026-04-28
Added
- Podcast compare mode — select 2-3 podcasts in the homepage table (new checkbox column) and a sticky "Vergleichen" pill routes to
/compare?ids=…, which renders side-by-side summary cards, an overlaid year-by-year line chart, and a metric table. - Real KPI trend deltas on the homepage:
Podcasts gesamt,Folgen gesamt, andFormate gesamtnow show signed Δ vs the previous analyzer run withTrendingUp/TrendingDownicons. Backed by a newweb/public/result-history.jsonljournal that grows by one line per run. - Tiny sparkline column in the homepage Podcasts table (recharts
<LineChart>, no axes/dots) showing each podcast's per-year episode counts at a glance. - JSON-LD
PodcastEpisodeschema.org block embedded on every episode page (full title, description, ISO 8601 duration, audio URL, cover image, parent series) for richer Google Podcasts / generic crawler results. - Generated Open Graph image per podcast (Tangerine accent, podcast name, total episodes + format counts) at
/[podcastId]/opengraph-image— child routes (categories, episodes) inherit it automatically. Shared episode links now render distinctive cards on Slack/Twitter/Mastodon. - OPML 2.0 feed export at
/feeds.opml, linked from the sidebar footer — one-click subscribe across podcatchers. - Playwright smoke suite (
pnpm test:e2e) covering tab persistence (?tab=…), Cmd+K palette navigation, and theme toggle. Runs in CI on everyweb/** PR. - Skip-to-content keyboard link at the top of the layout for screen-reader / keyboard users.
pnpm build:analyzescript wires@next/bundle-analyzerfor one-shot bundle inspection.
Changed
- Cmd+K palette now matches against episode descriptions (not just titles), mirroring the
/searchpage. Searching for "metacritic" or "braid" now surfaces the relevant Tri-bunal episodes. - The full stripped episode description (previously truncated to 280 chars) is now indexed for matching; a separate 280-char
snippetfield carries the visible preview. Search index grew to 9.7 MB raw / 2.34 MB gzipped. - EpisodeArchive on per-podcast pages restructured as a server component with a thin client
Accordionwrapper. Per-podcast HTML payloads dropped dramatically (gamenotover from ~13 MB → 808 KB; the largest, thepod, from ~14 MB → 8.8 MB). - Per-podcast
DownloadButtons now fetch/results.jsonon click and slice out their data, instead of embedding the per-podcastcategoryCountYearTitleblob into every page bundle. - Light-mode
--primarydarkened fromoklch(0.7 0.18 55)tooklch(0.6 0.2 50)so primary buttons / sidebar accent meet WCAG AA contrast (~4.7:1) against the near-white foreground. Dark mode unchanged. Documented inglobals.css. metadataBaseset on the root layout so OG / Twitter image URLs resolve against the canonicalhttps://rssanalyzer.org(overridable viaNEXT_PUBLIC_BASE_URL) instead ofhttp://localhost:3000.- The per-podcast page header dropped the now-redundant "Episoden Archiv" button (the new tab covers the same affordance) and renamed the third tab from "Archiv" to "Episoden Archiv".
Fixed
- Switching to the Übersicht tab on a per-podcast page no longer expands the layout horizontally —
SidebarInsetand the inner content column gainedmin-w-0, so the wide format-by-year table now scrolls inside itsoverflow-x-autowrapper. - Recharts no longer logs
width(-1) and height(-1) of chart should be greater than 0on mobile — the homepage Sparkline column dropped itsResponsiveContainer(it always renders at fixed pixel dimensions insidehidden md:flexcells). - Tab persistence (
?tab=…) now reliably clears when navigating back to the Übersicht tab — the soft-navigation race betweenrouter.replaceanduseSearchParamswas replaced with a directhistory.replaceStatecall.
Removed
- Vendored shadcn UI primitives that the dashboard-01 block install pulled in but the app never used:
ui/toggle.tsx,ui/toggle-group.tsx. Dropped peer deps@tabler/icons-react,@dnd-kit/{core,modifiers,sortable,utilities}, andzod.
[2.1.0] - 2026-04-28
Added
- Per-podcast page restructured around shadcn
Tabs(Übersicht / Charts / Episoden Archiv / Daten). The active tab persists in the URL via?tab=…(bookmarkable, browser-back works), and any#archiv-…deep-link auto-switches to the Episoden Archiv tab and opens the matchingAccordionitem. - Cmd+K / Ctrl+K palette now shows per-result-type shortcut chips (P / F / E) and a sticky kbd footer (
↑↓navigieren ·↵öffnen ·escschließen). - Pagination on the per-category episode list (
Pagination+ page-sizeSelect10/25/50/Alle), spanning all years with inline year separators so the page stays scannable on prolific formats. - HoverCard previews on long episode titles in the Episoden Archiv (cover thumbnail, date, duration, file size) and on truncated breadcrumb segments above 40 chars.
- Toast notifications (
Sonner) on JSON downloads from per-podcast pages. - Shape-aware
Skeletonplaceholders for every chart whileLazyMountdefers hydration — bar bars, area gradient, heatmap grid.
Changed
- Brand palette retuned to a Tangerine accent —
--primary,--ring, and--chart-1..5shifted to a warm orange/amber/yellow/teal/violet ramp (Slate neutrals untouched). Both light and dark modes adopted. PodcastsTableandPodcastFormatTablenow use@tanstack/react-tablefor sort/filter/pagination, replacing hand-rolleduseMemosort logic. Adds page-size selector (10/25/50/100/Alle) on the homepage table and a unifiedColumnDeftyped surface.- Homepage
DashboardStatsadopts the shadcndashboard-01block visual pattern — KPI tiles useCard+CardDescription+ tabular-numsCardTitle+CardActionBadge+CardFooterheadline/caption with a softfrom-primary/5 to-cardgradient. - Per-podcast page header trimmed to a single Homepage button (the Archiv tab supersedes the old "Episoden Archiv" anchor button).
- Episode info modal in the archive now uses a
ResponsiveDialogwrapper that renders a centered shadcnDialogon>=mdviewports and a bottom shadcnDrawer(vaul) on mobile. - Episode covers in the archive switched to
next/image(withunoptimizedfor the many third-party hosts), removing the bespokeoptimizeCoverUrl+ Photon hostname helper. - All "Keine Folgen" / "Keine Treffer" / "Mindestens 2 Zeichen" placeholders now render as shadcn
Alertblocks with lucide icons;error.tsxandnot-found.tsxrewritten asAlert+Buttoninstead of bespoke layouts. - Subtle 280 ms
card-inmount animation on top-levelCards for calmer page transitions (respectsprefers-reduced-motion).
Fixed
- Switching to the Übersicht tab no longer expands the layout horizontally —
SidebarInsetand the inner main column gainedmin-w-0, so the wide format-by-year table now scrolls within itsoverflow-x-autowrapper instead of pushing the whole page wider than the viewport.
[2.0.0] - 2026-04-28
Changed
- Full UI redesign on Tailwind CSS v4 + shadcn/ui (New York / Slate). Bootstrap 5 and the custom SCSS layer are gone. Every page, every component, and every chart now uses shadcn primitives —
Card,Table,Tabs,Dialog,Accordion,Breadcrumb,Badge,Input,Select,Switch,Button,Sidebar,Chart, andDropdownMenu. URL structure is preserved (/,/[podcastId],/[podcastId]/[categorySlug],/[podcastId]/[categorySlug]/[episodeId],/search,/changelog). - Navigation moved from a top navbar to a collapsible app
Sidebarwith two grouped sections — "Featured" (Mindestens 10 Zeichen, Stay Forever, The Pod) and "Alle Podcasts" (alphabetical) — plus a top bar with the sidebar trigger, a global Cmd+K search trigger, and the theme toggle. - Episode Archive replaced the native
<details>accordion + hand-rolled Bootstrap modal with shadcnAccordion(type="multiple", hash-deep-linking preserved) +Dialogfor the description modal. Episode rows now useAvatar+Badge(Paywall / MP3 / Duration / Filesize) + an InfoButton. - Charts (
PodcastDistributionChart,YearSumChart,MonthlyAverageChart,CumulativeChart,DurationHistogram,CategoryYearChart) wrap recharts in shadcnChartContainer/ChartTooltipContent/ChartLegendContentwithvar(--chart-1..5)CSS variables, so light/dark theming is automatic — no moreuseChartThemehook orMutationObserver. YearMonthHeatmaprebuilt on Tailwind utilities +color-mix(in oklch, var(--chart-1) …)for theme-aware cell intensity.- Search page (
/search) usesInput+Select+Tabsfilters withCardresult cells, type chips, andSkeletonplaceholders while the index loads. - Theme handling switched from Bootstrap's
data-bs-themeattribute + custom localStorage logic tonext-themes(Light / Dark / System) on<html class="dark">. The pre-hydration inline script is gone. - Changelog page wraps the rendered Markdown in a shadcn
Card+ Tailwindprose(via@tailwindcss/typography) for proper typography in both themes.
Added
Cmd+K/Ctrl+Kglobal command palette (shadcnCommandovercmdk) that fuzzes across podcasts, formats, and episodes from the existingsearch-index.jsonand routes directly to the relevant page.- Sortable shadcn
Tablefor the homepage Podcasts overview, with name search (Input), still-releasing toggle (Switch), and per-column sort buttons. The same pattern powers the per-podcast Format-by-Year matrix in the newPodcastFormatTable(replaces the formerSortableTable.jsx).
Fixed
- Per-page HTML payload reduced ~290× (from 21 MB to ~73 KB) by passing only a small
{id, name, featured}[]shape into the client-sideAppSidebarinstead of leaking the full 19 MBResultblob through RSC into every static page. - Recharts axis labels and tooltips already render correctly in dark mode on every chart — re-implemented via shadcn chart vars rather than the previous
useChartThemeworkaround (folds in the unreleased 1.22.1 fix).
Removed
bootstrap,sass, all custom SCSS (styles/bootstrap.scss,styles/elements/*.css,styles/resets.css).lib/useChartTheme.ts(superseded by shadcn chart CSS variables).components/SortableTable.jsx,components/Header.tsx(replaced byAppSidebar+SiteHeader).- The inline pre-hydration
themeBootstrapscript inapp/layout.tsx.
[1.22.1] - 2026-04-26
Fixed
- Recharts bar chart axis ticks, labels, gridlines, tooltips and legends are now readable in dark mode. Previously, Recharts rendered them with hard-coded near-black SVG fills that Bootstrap's
data-bs-theme="dark"cascade never reached, leaving the labels nearly invisible against the dark panel. A newuseChartThemehook (lib/useChartTheme.ts) readsdata-bs-themeoff<html>and live-updates via aMutationObserverso the palette flips instantly when the user toggles the theme. Applied to all five chart components (PodcastDistributionChart,MonthlyAverageChart,DurationHistogram,YearSumChart,CumulativeChart,CategoryYearChart) — axis stroke + tick fill, gridline stroke, tooltip background/border/text, legend text, hover cursor, and theCumulativeChartarea fill all swap between light and dark presets.
[1.22.0] - 2026-04-26
Added
- Linear-regression trendlines on the year-over-year bar charts: dotted grey line over
YearSumChart("Folgen & Formate pro Jahr" on each podcast page) andCategoryYearChart("Folgen pro Jahr" on each category page). Makes growth/decline visually obvious at a glance. Trendline is suppressed for series with fewer than 2 data points. - New
lib/stats.tsexports a tinylinearRegressionhelper, covered by 6 vitest cases (perfect line, noisy series, zero-variance fallback, decreasing slope, short-input edge cases). Test suite now stands at 76 across 6 files.
Changed
- Production build now runs
npm testas part ofprebuild, so the contract/parity/logic tests gate everynext build.
[1.21.1] - 2026-04-26
Changed
- Test suite expanded from 11 to 70 tests across 5 files, covering every JS-only logic file in the web project —
lib/dates.ts,lib/categories.ts,lib/episodes.ts, the helper functions inUtils.tsx, and a parity test that asserts the build-timegenerate-search-index.jsepisodeId/categorySlugimplementations agree byte-for-byte with their TS counterparts on a broad sample of inputs. React components remain out of scope. generate-search-index.jsnow exports its helpers viamodule.exportsand guards the side-effect block withif (require.main !== module) return;so vitest canrequire()it without triggering a file write. The CLI entrypoint behaviour (npm run generate-search-index) is unchanged.- Vitest test environment switched from
nodetojsdomso the DOM-dependent helpers inUtils.tsx(getDescriptionHtmland itsDOMParser-based linkifier chain) are testable;jsdomadded as a devDependency.
[1.21.0] - 2026-04-26
Added
- Dark mode — header toggle button cycles
light → dark → autowith localStorage persistence, an inline pre-hydration script applies the saved theme before paint to avoid flash-of-wrong-theme, and a[data-bs-theme="dark"]override block covers every custom color (tier nav badges, search type pills, filter chips, episode-neighbor cards, changelog code blocks, focus outline). - Next/previous episode navigation on episode permalink pages — the strip below the audio surfaces the chronological neighbors within the same format (with
rel="prev"/rel="next"for SEO), or shows a muted "Erste Folge / Aktuellste Folge dieses Formats" placeholder at the edges. Collapses to single column under 576 px. - Vitest test suite covering the slug/id/contract helpers —
getIdJava parity,categorySlugdiacritic handling and fallback,archiveSectionId,episodeIddeterminism,hasEpisodesedge cases, andassertMetadataIdsMatchdrift detection. Run withnpm test.
Fixed
- Episode descriptions are readable in dark mode — publisher-supplied descriptions routinely embed
style="color: #..."declarations that were invisible on a dark background. A scoped[data-bs-theme="dark"] .episode-description *:not(a) { color: inherit !important }override neutralizes inline colors without touching light mode.
[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