Software & System Updates

v9.3.0 – Sprint 10 abgeschlossen: Nostr-native Stack vollständig

  • build.py: Funkraum-Tab aufgeteilt in zwei Sub-Tabs — 📊 Telemetrie (OP3-Stats) und 💬 Bordfunk (ZapThreads); saubere Trennung statt Kraut-und-Rüben-Layout
  • app.js: setupSubTabs() — Sub-Tab-Switching mit ARIA (aria-selected, aria-hidden), analog zu setupTabs()
  • style.css: .subtab-nav, .subtab-btn, .subtab-panel — Cypherpunk-Sunrise-Style; aktiver Tab mit dawn-amber Unterstrich
  • build.py: _zapthreads_element() + _habla_hint_html() — neue Hilfsfunktionen; beide verwenden GUID-basierten Slug (identisch zu NIP-23 d-Tag — kein URL-Slug)
  • build.py: disable="relayInformation" auf <zap-threads> — deaktiviert NIP-11-Filterung die Relays fälschlich verwirft und Kommentare unsichtbar macht
  • config.py: NOSTR_SEARCH_RELAYS — neuer Key für Index-Relays; relay.nostr.band als Default (indexiert Events relay-übergreifend, findet kind:1 Announcements); kein Hardcode mehr in build.py
  • build.py: _zapthreads_element() liest NOSTR_SEARCH_RELAYS und dedupliziert mit NOSTR_RELAYS
  • build.py: Script-Quelle zapthreads.devunpkg.com/zapthreads/dist/zapthreads.iife.js (zapthreads.dev war offline laut PageSpeed); asyncdefer
  • build.py: <div class="zapthreads-container" data-uri="..."><zap-threads anchor="naddr1..." relays="..." user="npub1..." disable="relayInformation"> (div war nie funktional)
  • style.css: zap-threads { display:block; min-height:60px } + Lade-Indikator zap-threads:empty::before
  • style.css: .habla-hint — Bitcoin-Orange left border, subtiler Hintergrund, klickbarer Link
  • build.py: _build_nip94() — signiertes kind:1063-Event pro MP3; Tags: url, m (audio/mpeg), x (SHA256 lokal gehasht), size, duration, title, summary, image, r
  • build.py: NIP-94 vor NIP-23 gepublisht; Event-ID als ["e"]-Mention in NIP-23 referenziert
  • build.py: _build_nip1() — Announcement mit nackter MP3-URL (Primal-Player), ["a"]-Referenz, ["podcast:guid"]
  • build.py: kind:1 IMMUTABEL — Cache-Key nip1:{guid} wird niemals mit --force überschrieben
  • build.py: publish_to_nostr erhält make_meta_from_ep()-Dicts — episode_url, audio_url (OP3-bereinigt), cover_url korrekt
  • build.py: published_at via email.utils.parsedate_to_datetime (RFC-2822); ISO-8601-Fallback; kein time.time() mehr
  • build.py: OP3-Prefix-Stripping für ["url"]-Tag
  • build.py: MP3-URL als nackte URL auf eigener Zeile (kein Markdown-Wrapping — Primal rendert Audio-Player)
  • config.py: NOSTR_SSG_NAME, NOSTR_EPISODE_LINK_LABEL, NOSTR_HABLA_HINT editierbar; [naddr] → habla.news-Link
  • build.py: _bech32_encode() + _naddr_encode() — NIP-19 naddr in purem Python stdlib; TLV-Encoding
  • build.py: _blossom_auth_event(), _blossom_upload(), _blossom_url_from_local() — BUD-01 Upload; Authorization: Nostr <base64>
  • config.py: BLOSSOM_SERVERS, BLOSSOM_ENABLED (opt-in)
  • build.py: display=swapdisplay=optional für Bunny Fonts (~700ms LCP)
  • build.py: generate_thumbnails() — 120px JPEG+WebP (Pillow, LANCZOS); srcset="thumb 120w, full 744w" in ep-cover; ~340 KiB gespart
  • app.js: setupScrollReveal() — alle getBoundingClientRect()-Reads vor classList-Writes (Forced-Reflow-Fix)
  • app.js: setupParallax()offsetHeight per requestAnimationFrame nach erstem Paint
  • build.py: aria-label="{title} – Details" auf alle DETAILS →-Links

v9.0.2 – Boost vs. Zap + BOLT11-Invoice

  • app.js: openZapModal(addr, amountSats) — BOLT11 via LNURL-pay; QR zeigt Invoice; Boost-Button mit Betrag
  • app.js: _fetchBolt11(addr, sats) — fetcht LNURL-pay, prüft minSendable/maxSendable
  • style.css: .zap-modal__amount — 1.5rem Space Mono, Bitcoin Orange

v9.0.1 – V4V Panel + Radarbild Duplikat-Fix

  • style.css: .bp-v4v-panel max-height/overflow entfernt
  • app.js: renderEngineStatus() aus OP3Stats.render() entfernt (Dopplung auf statistiken.html)

v8.13.0 – Sprint 7 abgeschlossen: Critical CSS, Performance-Architektur

  • build.py: generate_critical_css() — neue Funktion extrahiert 20 Selektoren aus style.css (:root, Reset, html/body, Nav, Hero, Home-Header, Layout-Wrapper, .card) und minifiziert sie zu ~3KB; Kommentare entfernt, Whitespace kollabiert, Interpunktion gestrippt; Ergebnis wird einmal pro Build generiert und an alle build_*-Funktionen weitergegeben
  • build.py + make_head(): Critical CSS inline + non-blocking style.csscritical_css-Parameter in make_head() ergänzt; bei gesetztem Wert: <style> inline im <head> + style.css als media="print" onload="this.media='all'" (non-blocking); <noscript>-Fallback für JS-disabled-Umgebungen; ohne critical_css (leer): klassischer blockierender Link — kein Regressions-Risiko
  • build.py: Alle build_*-Signaturen erweitertbuild_changelog, build_index, build_archive, build_stats erhalten critical_css: str = ""; build() generiert einmal, befüllt tmpl_raw via {{CRITICAL_CSS}} und reicht durch; kein erneutes Lesen von style.css
  • src/template.html: {{CRITICAL_CSS}} Platzhalter — ersetzt während Build durch minifizierten Critical-CSS-String; style.css-Link auf non-blocking umgestellt; <noscript>-Fallback
  • make_head(): Twitter/X Card Tags vervollständigttwitter:image, twitter:title, twitter:description hinzugefügt (fehlten seit v1.0.0); gilt jetzt für alle Seiten die make_head() nutzen (Changelog, Index, Archiv, Statistiken)
  • docs/ROADMAP.md: Sprint 7 → Abgeschlossen — alle [x]-Punkte dokumentiert; Lighthouse-Messung und Folge 8 als [>] nach Sprint 9 verschoben; Sprint 9 um „Von Sprint 7 übernommen"-Block ergänzt

v8.12.5 – Table-Scroll-Fix, Share-Bild, Twitter-Cards

  • assets/css/style.css: .card { overflow: clip } statt overflow: hiddenhidden blockiert overflow-x: auto in Kindelementen (erzeugt BFC der Scroll-Container deaktiviert); clip schneidet visuellen Overflow ab ohne BFC; .card::before Noise-Texture und box-shadow bleiben korrekt; horizontaler Table-Scroll auf Mobile jetzt funktional
  • src/template.html: Web Share API Level 2 — Share-Handler holt Episodenbild via fetch(), übergibt als File in files[]; navigator.canShare() Guard; dreistufige Fallback-Kette (Level-2 → Basic → Clipboard); cache: 'force-cache' verhindert doppeltes Laden
  • build.py: data-share-img auf Share-Button ergänzt → enthält absolute URL des Episodencovers (meta['img_url'])
  • src/template.html: Twitter/X Card Tagstwitter:image, twitter:title, twitter:description hinzugefügt; Nostr-Clients und Mastodon nutzen diese für Link-Previews

v8.12.4 – Mobile Tabelle, WCAG Labels komplett, Inline-Styles bereinigt

  • assets/css/style.css: Tabelle Mobile-Fixwidth:100%width:max-content; min-width:100% damit .table-scroll tatsächlich horizontal scrollt; border-radius vom table-Element auf den Wrapper verlagert (Tabellen-Border-Radius funktioniert nur mit overflow:hidden was Scroll blockiert); Scroll-Schatten-Gradient als visueller Hinweis auf horizontalen Inhalt
  • assets/css/style.css: WCAG-Labels komplett durchgezogen — alle verbliebenen Sub-WCAG-Schriften auf Minimum gebracht: kpi-card-label 0.44→0.65rem, kpi-card-sub 0.42→0.62rem, eng-kpi-label 0.42→0.62rem, kpi-stat-label 0.40→0.62rem, roadmap-kpi-label 0.50→0.62rem, werft-section-title 0.48→0.65rem, op3-snapshot-ts 0.46→0.65rem; --text-faint auf dekorativen Elementen behalten, auf lesbarem Text durch --text-dim ersetzt
  • assets/css/style.css + assets/js/app.js: Inline-Styles vollständig in CSS-Klassen ausgelagert — 8 Inline-Style-Attribute entfernt: .toast-title/.toast-msg, .bp-skip-icon, .lo-btn-reaction/.lo-btn-close, .settings-group--gap, .op3-setup-hint, .op3-snapshot-ts, .werft-section-title--gap; einzig verbliebener Inline-Style: bp-transcript-scroll font-size (User-Preference, Laufzeit-Wert, nicht in CSS ausdrückbar)

v8.12.3 – WCAG 2.1 AA: Kontrast & Schriftgrößen

  • assets/css/style.css: --text-dim Opacity 0.50 → 0.65 — auf --surface-2 (#161616) ergab 50% Opacity nur 4.48:1 (AA-Grenze: 4.5:1); mit 65% überall ≥ 6.7:1; --text-faint und --text-ghost bleiben unverändert (dekorativ, kein Textinhalt)
  • assets/css/style.css: KPI-Labels vergrößertop3-kpi-label 0.48rem → 0.65rem; werft-kpi-label 0.44rem → 0.62rem; 7px Uppercase-Text ist unter jedem Kontrast-Standard nicht lesbar; Größen jetzt im lesbaren Bereich (≥ 9.9px) bei Kontrast ≥ 4.5:1
  • assets/css/style.css: Tagline 0.62rem → 0.70rem; section-label 0.58rem → 0.68rem — gleiche Begründung; Dawn-Orange auf --bg hat 6.68:1, auf --surface-2 noch 6.00:1; alle Kombinationen PASS
  • Gemessene Kontrast-Ratios nach Fix: text-dim auf bg 7.47 · auf surface 7.16 · auf surface-2 6.71 · dawn auf bg 6.68 · dawn auf surface-2 6.00 — alle ≥ 4.5:1 (WCAG 2.1 AA)

v8.12.2 – Parallax-Fix, Font-Verbesserungen

  • assets/css/style.css: overflow:hidden auf .hero-section wiederhergestellt — notwendig damit der ::after-Gradient-Schleier das per translateY bewegte Bild beim Scrollen abdeckt; ohne es "schwebt" der Schleier statisch während das Bild darunter verschwindet; Anti-Pattern in AGENTS.md präzisiert: gilt nur ohne Parallax
  • assets/js/app.js: setupParallax() aus requestIdleCallback herausgenommen — Parallax muss synchron im DOMContentLoaded laufen damit der erste Scroll-Event sofort greift; Ripple/ScrollReveal/TranscriptScroll bleiben im idle()-Block
  • assets/css/style.css: KPI-Zahlen auf var(--font-mono) (Space Mono)op3-kpi-val und werft-kpi-val nutzten Syne 800 (Display-Schrift, für Zahlen unruhig); Space Mono mit font-variant-numeric: tabular-nums ist lesbarer und zum Terminal-Ästhetik des Projekts passend
  • assets/css/style.css: Hero-Titel größer und engerclamp(1.4rem,6vw,2rem)clamp(1.6rem,7vw,2.2rem), letter-spacing: -0.02em; mehr Präsenz auf kleinen Displays
  • assets/css/style.css: Shownotes-Headings verbessert — h2 mehr Margin-top (1.4em), h3 font-weight: 600 statt 700 für klarere Hierarchie

v8.12.1 – Quality-Check: 5 Bugs behoben, Sprint 7 komplettiert

  • build.py: En-Dash/Pfeil in Nicht-print-f-strings (2 Stellen) — L1156 "—""-" (ASCII), L2090 f"... → ..." → String-Konkatenation mit " -> "; Termux SyntaxError-Risiko beseitigt (AGENTS.md Anti-Pattern)
  • assets/css/style.css: overflow:hidden auf .hero-section entfernt — L131 war aktives AGENTS.md Anti-Pattern; unterbrach den Gradient-Blend-Effekt des ::after-Overlays; jetzt nur noch position:relative; width:100%
  • assets/js/app.js: 22 <button> ohne type="button" — alle per Regex ergänzt; betroffen: Bottom-Player-Toolbar (Mini + Sheet), Zap-Modal, Like-Overlay, Bookmark-Buttons; HTML5-valide, kein ungewolltes Form-Submit
  • assets/js/app.js: requestIdleCallback für visuelle EffektesetupScrollReveal(), setupParallax(), setupRipple(), setupTranscriptScroll() jetzt via requestIdleCallback({timeout:400}); Fallback setTimeout(0) für Safari < 16; Main-Thread nach LCP frei
  • assets/js/app.js: Engine-Status-Sektion in statistiken.html (Sprint 7 p1) — renderEngineStatus() in StatsPage.render() ergänzt; zeigt COMMITS, LINES OF CODE, LETZTEN SHA, STARS (Codeberg live), BUILD-ZEIT als op3-kpi-row; graceful degradation: Abschnitt erscheint nur wenn engineData Werte enthält (v8.12.0 Voraussetzung erfüllt)

v8.12.0 – Sprint 7: engineData auf Statistiken, Share-Button & SW-Strategie

  • build.py: make_hu_config()stats_page-Parameterstatistiken.html erhält jetzt vollständiges engineData (git-Metriken + Codeberg-API) im window.HU_CONFIG; claudeData und KPI-Details bleiben changelog.html-only (Privacy-Policy: Codeberg-Repo-Daten sind öffentlich, Arbeitszeit-Schätzungen nicht)
  • build.py: build_stats() ruft make_hu_config(stats_page=True) — engineData mit git.commits, git.loc, git.lastSha, codeberg.stars, buildTs und goals jetzt in __STATS_DATA__ auf der Statistik-Seite verfügbar (Grundlage für Engine-Status-Sektion in app.js)
  • build.py: navigator.share() auf Episodenseiten — Invite-Button nutzt data-share-url + data-share-title-Attribute statt inline onclick="copyRSS()"; CSS-Klasse btn-share für zukünftiges Styling
  • src/template.html: Share-Handler via Event-Delegation — kleines IIFE bindet [data-share-url]-Elemente an navigator.share() (nativ) mit navigator.clipboard-Fallback (kein inline-onclick, HTML5-valide, kein app.js-Coupling)
  • build.py: sw.js – Zwei-Strategie-Fetch — Episode-Seiten (/episode/*.html) nutzen stale-while-revalidate (sofortiger Cache-Hit + Hintergrund-Revalidierung); alle anderen Assets weiterhin Netzwerk-zuerst mit Cache-Fallback
  • build.py: Tabellen in Shownotes mit <div class="table-scroll"> umhüllt — kein horizontales Abschneiden auf mobilen Displays mehr
  • assets/css/style.css: .table-scrolloverflow-x: auto für alle Shownotes-Tabellen
  • docs/ROADMAP.md: Sprint 10 — Nostr-native Strategie (NIP-23, ZapThreads, castr.me)

v8.11.2 – Feed-Qualität, Code-Hygiene & HTML5-Validierung

  • build.py: <description> limitiert — auf 300 Zeichen begrenzt (Satz-sauber), direkt mit Link zur Episodenseite angehängt (→ https://...)
  • build.py: <content:encoded> als Preview — nur Intro-Abschnitt (vor erstem <hr>) statt vollständigem HTML; Folge 7: 12.172 → 750 Zeichen; Link → Shownotes & Ressourcen an Intro angehängt
  • build.py: Relative #-Anker in content:encoded zu absoluten URLshref="#lifehacks-details"href="https://...episode.html#lifehacks-details"; RSS-Reader kennt keine Basis-URL (W3C Feed Validator Empfehlung behoben)
  • build.py: podcast:value pro Item — optionales YAML-Feld v4v_recipients in Frontmatter (Schema v2.2); fehlt → Channel-Level gilt; vorhanden → Item-Block überschreibt für diese Episode (z.B. Gast-Split); Split-Summen-Validierung mit ⚠️-Warnung
  • build.py: <a name="..."></a><span id="..."></span> — HTML5-invalid (name obsolet, href fehlt); Fix: <span id="..."> erzeugt validen Anker; W3C HTML Checker Fehler behoben
  • build.py: Anchor-Links # ohne target="_blank"_link() in parse_shownotes_md(): href="#..." öffnet keinen neuen Tab mehr
  • build.py: Bare except: gefixt_season_sort(): except:except (ValueError, TypeError):; fing zuvor SystemExit und KeyboardInterrupt
  • build.py: DeprecationWarning beseitigtdatetime.utcnow()datetime.now(tz=timezone.utc); datetime.utcfromtimestamp()datetime.fromtimestamp(..., tz=timezone.utc); timezone zu Imports hinzugefügt; Python 3.14-sicher
  • src/shownotes/8_*PLATZHALTER.md: Schema v2.2, v4v_recipients dokumentiert als auskommentiertes Beispiel
  • version: 8.10.18.11.2 (war seit mehreren Sessions veraltet)

v8.11.1 – Debugging & Code-Qualität: 3 Bugs behoben

  • build.py: Bug: seo_description war totes Feldseo_description aus Frontmatter-Schema v2.1 wurde weder in den RSS-Feed geschrieben noch von build_episodes() gelesen; fix: build_feed() schreibt es jetzt als <itunes:summary>, get_episode_meta() liest itunes:summary bevorzugt als desc_meta fuer HTML <meta name="description">; Fallback auf sanitize_meta(desc_raw) wenn Feld fehlt
  • build.py: Bug: EN-DASH in f-stringf"{CONFIG['PODCAST_NAME']} – Statistiken" in build_stats() enthielt U+2013 EN-DASH im f-string-Literal; per AGENTS.md Konvention verboten (Python 3.12 Termux-Risiko); fix: einfache String-Konkatenation mit ASCII-Bindestrich
  • docs/CHANGELOG.md: Bug: Falsches Heading-Level## v8.10.1 hatte nur ein # statt zwei; get_version() und parse_changelog_md() erwarten ## vX.Y.Z; fix: Heading-Level korrigiert

v8.11.0 – RSS-Generator: feed.xml aus Shownotes-Frontmatter

  • build.py: build_feed(version) – generiert feed.xml vollautomatisch aus src/shownotes/*.md Frontmatter; manuelle XML-Bearbeitung entfällt
  • build.py: parse_frontmatter(text) – YAML-Frontmatter-Parser mit Typ-Inferenz (bool, int, str); Leerzeilen im FM korrekt behandelt
  • build.py: load_episodes_from_shownotes() – scannt src/shownotes/*.md, überspringt Dateien ohne gültiges Frontmatter
  • build.py: _date_to_rfc(date_str) – ISO-8601 mit Timezone (2026-03-16T12:00:00+01:00) korrekt nach UTC/GMT umgerechnet; v2.0-Datumsformat weiterhin unterstützt
  • build.py: _xml_escape() – eigenständige Escape-Funktion für RSS-XML (kein Namespace-Konflikt mit html.escape)
  • build.py: load_shownotes_md() – Lookup via audio_url statt transcript_url; Frontmatter wird automatisch entfernt
  • build.py: inline() in parse_shownotes_md()***bold+italic*** erzeugte ungültige Verschachtelung <strong><em>text</strong></em>; Fix: *** Pattern vor ** geprüft
  • build.py: build() – ruft build_feed() als ersten Schritt auf; danach wird feed.xml geparst wie bisher
  • build.py: int() auf audio_bytes/audio_dauer in try/except gewrappt – kein Crash bei fehlerhaftem Frontmatter-Wert
  • build.py: HTML5-Bug: {{PAGE_TITLE}} in <title> und og:titlemeta['title'] mit & (z.B. „Bitcoin & KI") erzeugte W3C-Fehler; Fix: html.escape() auf Titel angewendet
  • config.py: Neue Sektion 0. RSS FEEDFEED_TITLE, FEED_DESCRIPTION, FEED_ITUNES_SUMMARY, FEED_LANGUAGE, FEED_AUTHOR, FEED_EMAIL, FEED_CATEGORIES, FEED_COVER_URL, FEED_LIGHTNING_*, FEED_NOSTR_NPUB
  • config.py: Schema v2.1 – SHOWNOTES_SCHEMA_VERSION: "2.1" als dokumentierter Key; veraltete Sprint-Warnungen bereinigt
  • src/shownotes/: Alle 8 Episoden auf Schema v2.1 angehoben – schema_version, seo_description, enclosure_type, datum mit Uhrzeit und Timezone
  • src/shownotes/8_*.md: Folge-08-Vorlage mit vollständigem v2.1-Schema und erklärenden Kommentaren; wird beim Build übersprungen bis audio_bytes > 0
  • Shownotes Folgen 1–7 + Trailer: Vollständige Body-Texte mit Tabellen, Blockquotes, Code-Blöcken und Troubleshooting-Sektion (Android chmod-Problem)

v8.10.1 – ISSUES.md: Vollständige Bughistorie nachgeführt

  • docs/ISSUES.md: Komplett neu geschrieben — 47 dokumentierte Issues aus der gesamten Projekthistorie (v6.x–v8.10.0)
  • Abgedeckt: s0 kritisch (1), s1 hoch (10), s2 mittel (27), s3 niedrig (9)
  • Alle behobenen Bugs aus CHANGELOG.md und AGENTS.md Anti-Pattern-Sektion übertragen
  • Jede Eintrag mit Ursache, Reproduzierbarkeit und Fix-Beschreibung

v8.9.0 – Vertikaler Rhythmus + Tabellen-Design + Font-System

  • style.css: Vertikaler Rhythmus — .section-divider 22px → 44px, .werft-section 32px → 52px, .log-entry 24px → 36px, .changelog-section 14px → 28px, .kpi-accordion 24px → 40px — mehr Luft zwischen allen Sektionen
  • style.css: Tabellen (shownotes-table) komplett überarbeitet — Zebra-Muster (odd/even rows), sichtbare border-warm Außengrenze, --font-mono Headers mit dawn-amber Farbe, Hover-State, letzte Zeile ohne border
  • template.html: Bunny CDN — outfit ersetzt durch plus-jakarta-sans:300,400,500,600,700
  • style.css: Font-Tokens in :root--font-body, --font-display, --font-mono als einzige Steuerstelle
  • style.css: Alle font-family-Inline-Strings auf var(--font-*) umgestellt
  • style.css: font-variant-numeric: tabular-nums lining-nums für alle KPI/Metric-Elemente
  • style.css: Header-Kommentar auf v8.9.0 aktualisiert

v8.7.1 – MD-first Shownotes + Tab-Bug gefixt

  • build.py: load_shownotes_md(slug) — lädt assets/texts/shownotes/<slug>.md als bevorzugte Shownotes-Quelle
  • build.py: parse_shownotes_md(md) — Markdown→HTML Konverter (code blocks, tabellen, h1-h3, listen, bold/italic, links, keywords)
  • build.py: build_episodes() — MD-Datei bevorzugt, RSS desc_raw als Fallback
  • build.py: hidden-Attribut aus tab_content entfernt — [hidden] { display: none !important } in CSS hat JS-Tab-Handler überschrieben

v8.6.0 – KI-Assistenz statt Claude-spezifisch

  • config.py: CLAUDE_* Keys umbenannt zu KI_* — mehrere LLMs im Einsatz
  • config.py: KI_TOOLS (Freitext, z.B. "Claude, ChatGPT") statt CLAUDE_MODEL + CLAUDE_PLAN
  • config.py: KI_HOURS (KI-Stunden, großzügig schätzen) + KI_HOURS_HUMAN (eigene Stunden)
  • build.py: claudeData liest jetzt KI_* Keys
  • app.js: Werft-Sektion heißt „CO-PILOT — KI-ASSISTENZ", ABO-Card entfernt, KI-TOOLS-Card neu
  • Mensch-Stunden jetzt gold hervorgehoben — gleichwertig zur KI-Zeit

v8.5.0 – Claude-Sektion: CSV raus, manuelle Pflege

  • claude.ai Free/Pro Abo hat keinen API-Zugang → kein Token-Export möglich
  • config.py: CSV-Keys entfernt (CLAUDE_USAGE_CSV, CLAUDE_TOKENS_PER_HOUR)
  • config.py: neue Keys CLAUDE_SESSIONS, CLAUDE_HOURS_AI, CLAUDE_MODEL, CLAUDE_SINCE, CLAUDE_PLAN
  • build.py: make_hu_config()claudeData direkt aus config, kein parse_claude_csv() Aufruf mehr
  • app.js: renderClaudeSection() komplett neu — 6 Cards (Sessions, KI-Std., Mensch-Std., Modell, Abo, Aktiv seit), Arbeitszeit-Split-Balken, Hinweistext
  • Token/Kosten-Felder vollständig entfernt: kein inputTokens, costUsd, cacheRead mehr
  • Ehrlichkeit vor Vollständigkeit: Schätzwerte klar als solche kennzeichnen

v8.4.0 – Privacy: Sensible Daten nur noch auf changelog.html

  • Problem: claudeData (Kosten, Tokens), changelogKpis, issuesKpis, engineData (SHA, Timestamps) waren im HTML-Source jeder Episodenseite sichtbar
  • make_hu_config(): neuer Parameter changelog_page=False
  • Auf Episoden/Index/Archiv/Statistiken: claudeData=null, changelogKpis=null, issuesKpis=null, werftIntro=""
  • engineData auf Nicht-Changelog-Seiten: nur {buildMonth: "YYYY-MM"} — kein SHA, kein Unix-Timestamp, keine Git-Metriken
  • build_changelog(): einzige Funktion die changelog_page=True übergibt
  • Ergebnis: Kosten, Arbeitszeitmuster, Git-Fingerabdruck, Session-Daten nicht mehr reproduzierbar aus Episode-Quelltext

v8.3.0 – KPI-Panels: Logbuch, Issues, Claude AI in der Werft

  • build.py: parse_claude_csv() — liest Anthropic Console CSV-Export, aggregiert Tokens/Kosten/Modelle
  • build.py: calc_changelog_kpis() — berechnet Velocity, ⌀ Release-Abstand, Features vs. Bugfixes, aktive Tage
  • build.py: calc_issues_kpis() — berechnet Fix-Rate, S0/S1 offen, Bug:Feature-Ratio aus ISSUES.md
  • build.py: HU_CONFIG um claudeData, changelogKpis, issuesKpis erweitert
  • app.js: KpiPanels IIFE — drei Renderer: renderLogbuchKpis, renderIssuesKpis, renderClaudeSection
  • Logbuch-Tab: eingeklappter Akkordeon-Block „FLUGDATEN-ANALYSE" oben, mit Intro + 3 Cards + Stat-Strip + Legende
  • Issues-Tab: eingeklappter Akkordeon-Block „QUALITÄTSPRÜFUNG" oben, gleiche Struktur
  • Werft-Tab: neue Sektion „CO-PILOT — CLAUDE AI NUTZUNG" — 8 KPI-Cards + Arbeitszeit-Split-Balken
  • Arbeitszeit-Split: KI-Stunden (Tokens ÷ konfigurierbarer Rate) vs. menschliche Stunden (manuell)
  • CSV-Hinweis wenn docs/claude_usage.csv fehlt — mit Link zur Anthropic Console
  • config.py: CLAUDE_USAGE_CSV, CLAUDE_HOURS_HUMAN, CLAUDE_SESSIONS_MANUAL, CLAUDE_TOKENS_PER_HOUR, CLAUDE_INTRO
  • config.py: LOGBUCH_KPI_INTRO, ISSUES_KPI_INTRO
  • docs/claude_usage.csv: Beispieldatei im Anthropic-Console-Format (austauschabr)
  • CSS: .kpi-accordion, .kpi-card-row, .kpi-stat-strip, .claude-hours-track, .claude-csv-hint

v8.2.1 – HTML5: Letztes Sprint-Div nicht geschlossen (content-body Bug)

  • parse_roadmap_md(): letztes Sprint-<div> wurde nie geschlossen — kein --- nach dem letzten Sprint in ROADMAP.md ausreichend
  • Fix: if in_sprint: html += "</div>" am Funktionsende
  • Ergebnis: <div class="content-body"> korrekt geschlossen, </main> valide

v8.2.0 – HTML5 Validierung: Stray-Divs und Section-Fehler behoben

  • parse_issues_md(): in_card-Tracking — --- schließt nur wenn eine Karte offen ist
  • parse_issues_md(): H1-Zeilen (# Datei-Header) werden übersprungen statt als Body-Text gerendert
  • parse_issues_md(): Letztes Issue wird korrekt geschlossen (offene Karte am Funktionsende)
  • parse_roadmap_md(): in_sprint-Tracking — --- schließt nur wenn ein Sprint-Div offen ist
  • build_changelog(): <section> in Roadmap/Issues/Werft-Tabs durch <div> ersetzt (W3C: section ohne direktes Heading)
  • Ergebnis: 0 W3C-Fehler in changelog.html (stray div, unclosed section, stray section — alle behoben)

v8.1.0 – Tab-System Grundsanierung + Werft Kontrast

  • Tab-Bug endgültig behoben: komplett neues System, identisch zu den funktionierenden Episode-Sheet-Tabs
  • CSS: .log-tab-panel { display: none !important } — Panels standardmäßig unsichtbar
  • CSS: .log-tab-panel.is-active { display: block !important } — aktiver Tab schlägt alles
  • JS: Event-Delegation auf dem Nav-Container (nav.addEventListener('click', ...) + closest())
  • JS: kein style.display, kein hidden-Attribut — nur classList.add/remove('is-active')
  • hidden-Attribute aus allen Panel-Divs im HTML entfernt — nicht mehr nötig
  • Paginierung: .is-hidden-Klasse statt hidden-Attribut für „Ältere laden"
  • Werft Kontrast: Intro-Text, Tabellen-Keys, Section-Titles, Gauge-Labels deutlich lesbarer

v8.0.0 – Werft: Vierter Tab mit Technischer Inspektion

  • Neuer Tab "🛠️ Werft" auf changelog.html — Technische Inspektion des Repos
  • config.py: CHANGELOG_TAB_4_ICON/LABEL, WERFT_INTRO, WERFT_GOAL_*, CODEBERG_REPO_URL
  • Wartungshandbuch-Ton im Einführungstext — voll aviation-metaphorisch
  • 4 Visualisierungsebenen: KPI-Cards · SVG-Halbkreis-Gauges · Progress-Bars · Repo-Tabelle
  • ⓘ-Button pro KPI-Card und Tabellen-Zeile öffnet kontextbezogenen Tooltip
  • SVG-Gauges mit Gradient-Bogen für Commits, LOC und Stars gegen konfigurierbare Zielwerte
  • Progress-Bars mit Dawn-Gradient-Fill und accessibility-konformem role=progressbar
  • CTA-Button am Ende des Panels: direkter Link zum Codeberg-Repo
  • Live-Indikator im Header: ● LIVE (Codeberg API) vs. ○ CACHED (--local Build)
  • Engine-Status-Panel aus Roadmap-Tab entfernt — Roadmap ist jetzt wieder sauber
  • build.py: werftIntro + engineData.goals + engineData.repoUrl in HU_CONFIG
  • Fallback-Werte für alle Metriken wenn Codeberg offline (--local Build)
  • Tab-System: vierter Tab data-logtab="werft"id="tab-werft", bestehende Tab-Logik unverändert

v7.9.0 – Tab-Fix: hidden-Attribut statt style.display

  • Root Cause aller Tab-Bugs gefunden: [hidden]{display:none!important} schlägt laut CSS-Kaskade auch Inline-Styles
  • initLogTabs(): removeAttribute('hidden') + setAttribute('hidden','') statt nur style.display
  • initLogPagination(): gleiche Korrektur für Einträge und "Ältere laden"-Button
  • Sheet-Tabs (Kapitel/Transcript): gleiches Muster durchgezogen
  • Fix gilt für alle Elemente die JS-seitig ein- und ausgeblendet werden

v7.8.0 – Ultimativer Code-Audit & Engine-Status

  • HTML5-valider Code verifiziert: alle Seiten bestehen strukturellen Audit (DOCTYPE, lang, aria, heading-Hierarchie, Alt-Texte, keine Duplikat-IDs)
  • make_head(): og:locale=de_DE, robots=index,follow, app.js defer — Performance-First
  • Keine redundanten <script>-Tags mehr im Body: app.js einmalig via <head defer>
  • statistiken.html: defer-kompatibler Init-Guard (readyState-Check statt blindem DOMContentLoaded)
  • Media Session API: Lockscreen-Controls (Play/Pause/Seek ±30s) bei jedem Episode-Load gesetzt
  • EngineStatus-Panel im Roadmap-Tab: 8 Aviation-Style KPI-Cards mit Tooltips
  • fetch_git_metrics() + fetch_codeberg_data() vollständig memoized — je 1x pro Build
  • SW-Template: SKIP_WAITING message listener (Kanal: event.data.type === 'SKIP_WAITING')
  • SW-Update-Toast: role=status, aria-live=polite — screenreader-kompatibel
  • CSS v7.8.0: Engine-KPI-Cards, SW-Toast, box-shadow Anti-Pattern bereinigt
  • Alle Fixes aus v7.7.x in sauberem Stand konsolidiert

v7.6.7 – Doppelter Play-Handler entfernt

  • Play-Button auf Home und Archiv startete Audio sofort und stoppte es wieder — zwei Handler feuerten gleichzeitig auf denselben Button
  • build.py: Inline-Script aus build_index() vollständig entfernt — setupEpisodeCards() in app.js ist alleinige Quelle für Card-Interaktionen
  • app.js: Player.inject() gegen doppeltes Einbinden gesichert (if (document.getElementById("bp")) return)
  • app.js: is-active Klasse wird jetzt auch auf .latest-episode-card gesetzt
  • app.js: Console-Log zeigt aktuelle Version aus HU_CONFIG.version statt hardcoded

v7.6.6 – Player-Bar auf Home und Archiv sichtbar

  • Player-Bar am unteren Rand erschien auf Startseite und Hangar nie — Audio lief, aber keine Kontrolle möglich
  • app.js: Player.inject() wird jetzt bedingungslos aufgerufen statt nur auf Episodenseiten
  • build.py: Hangar-Seite komplett neu gebaut — keine Featured-Card mehr, alle Episoden gleichwertig
  • build.py: Staffeln werden automatisch aus itunes:season im Feed erkannt und gruppiert
  • app.js: Settings-Panel mit Speed- und Sleep-Timer-Zeilen als erste Einträge
  • app.js: Transcript-Panel mit eigenem Mini-Header, Cover, Schriftgröße +/-, Auto-Scroll-Pause
  • style.css: Transcript-Vollbildmodus (.bp-sheet.transcript-mode) — Cover und Controls ausgeblendet

v7.5.1 – Transcript-Fix, Funkraum trimmen, KPI-Padding

  • app.js: Transcript im Player-Sheet geladen nicht — initSheetTabs() lief bei
  • app.js: Funkraum auf Episodenseiten auf 3 Bereiche reduziert:

v7.5.0 – Sprint 6 P1: Robustheit (OP3 Error Handling, Cache, set -e)

  • build.py: fetch_op3_data() graceful degradation — alle API-Calls außer
  • build.py: OP3 JSON-Cache (.op3_cache.json) — nach jedem Prod-Build gespeichert;
  • build.py: _load_op3_cache() + _save_op3_cache() als eigenständige Funktionen
  • config.py: OP3_CACHE_MAX_AGE = 3600 — konfigurierbar; 0 = immer frisch fetchen
  • abflug.sh: set -e + set -o pipefail — kein Push bei fehlgeschlagenem Build;

v7.4.2 – Nav-Fix Episodenseiten, Player-Guard, Abonnieren-Box

  • template.html: Nav hatte noch 6 Links mit Anker-URLs — jetzt konsistent 4
  • app.js: Player.inject() lief auf ALLEN Seiten → "keine Datei ausgewählt"
  • build.py: Abonnieren-Box (Fountain/Spotify/Apple/RSS) auf Episodenseiten

v7.4.1 – ROADMAP.md: Prioritäten, Checkboxes, Farbsystem

  • ROADMAP.md: Vollständig neu strukturiert mit Tag-Syntax:
  • build.py: parse_roadmap_md() — Parser erkennt alle Tags, setzt HTML-Klassen,
  • style.css: Roadmap-Farbsystem komplett neu (Sprint-Status-Border, Tag-Badges,
  • Sprint-Blöcke: roadmap-sprint-next (orange border) / -wip (blau) / -done (gedimmt)
  • Inline \code\` in Roadmap-Items wird als .roadmap-code` hervorgehoben

v7.4.0 – Nav-Reform, Footer-Fix, Logbuch-Tabs, Dev-Logbuch

  • build.py: Nav auf 4 echte Seiten reduziert — Anker-Links "Boardkarte"
  • build.py: Footer auf statistiken.html fehlte vollstaendig — jetzt eingebunden
  • build.py: changelog.html neu mit Tab-Switcher (Logbuch ↔ Roadmap)
  • build.py: Logbuch-Tab: Paginierung — 8 Eintraege sichtbar, Rest per
  • build.py: Roadmap-Tab: DEV_SESSIONS aus config.py als Balkendiagramm +
  • config.py: DEV_SESSIONS Liste ergaenzt (5 Sprints, 26.5h gesamt)
  • config.py: NAV_2/NAV_3 als Anker-Labels auf index.html dokumentiert
  • app.js: LogTabs Modul — Tab-Umschaltung + Pagination (IIFE, DOMContentLoaded)
  • style.css: .log-tab-nav/.log-tab-btn, .dev-session-*, .roadmap-kpi-*,

v7.3.3 – W3C HTML5 Validator: Font-URL fix

  • build.py: | in Bunny Fonts CSS-URL → %7C (URL-encoded), &&amp;

v7.3.2 – Vollständige CSS-Trennung + Repo-Hygiene

  • build.py: Letzte 4 inline style= entfernt (card-center, tagline-center,
  • app.js: Alle 10 fixbaren .style.cssText → CSS-Klassen (op3-mom-row,
  • style.css: 7 neue Klassen ergaenzt fuer alle migrierten Inline-Styles
  • .gitignore: __pycache__/, *.pyc, *.pyo, pages_repo.zip,
  • abflug.sh: git config push.autoSetupRemote true vor Push gesetzt —

v7.3.1 – Audit: Separation, Versions, Docs

  • build.py: Alle inline style="..." in build_stats() -> CSS-Klassen
  • build.py: statistiken.html Dateiname via CONFIG["STATS_PAGE_FILENAME"]
  • build.py: Version-Kommentar aktualisiert auf v7.3.0
  • build.py: window.HU_CONFIG um statsPage erweitert (JS liest
  • config.py: STATS_PAGE_FILENAME neu (default: "statistiken.html")
  • config.py: Version-Kommentar aktualisiert auf v7.3.0
  • app.js: Version-Kommentar aktualisiert auf v7.3.0
  • app.js: Hardcoded "Herr Urlaub..." -> window.HU_CONFIG.podcastName
  • app.js: Hardcoded /statistiken.html -> window.HU_CONFIG.statsPage
  • app.js: 5x .style.cssText -> CSS-Klassen (.op3-chart-xrow,
  • style.css: Version-Kommentar aktualisiert auf v7.3.0
  • style.css: .op3-chart-xrow, .op3-chart-xlabel, .op3-trend-row,
  • AGENTS.md: Vollstaendig neu fuer v7.3.0 (OP3-Sektion, StatsPage-Doku,
  • README.md: Aktualisiert auf v7.3.0, Badge, Statistiken-Dashboard-Tabelle

v7.3.0 – Statistiken-Seite + Episodenseite Kompakt-Panel

  • build.py: build_stats() generiert vollständiges OP3 Mega-Dashboard
  • Sektionen: Überblick KPIs · Wachstumskurve (tagesgenaue Zeitreihe) ·
  • Episode-Liste mit Metadaten aus Feed + OP3-Stats zusammengeführt
  • window.__STATS_DATA__ = {show, episodes:[{title,slug,d1,d3,d7,all},...]}
  • app.js: window.StatsPage Modul — vollständig eigenständig
  • config.py: NAV_6_TEXT = "Radarbild" → /statistiken.html
  • template.html, make_nav(): 6. Nav-Eintrag
  • Sitemap + SW-Cache um statistiken.html erweitert
  • Kompakt: nur Gesamt / 7d / 24h + Momentum-Badge
  • "Alle Statistiken →" Link zu /statistiken.html
  • .stats-ep-list, .stats-ep-row, .stats-ep-rank, .stats-ep-title,

v7.2.9 – OP3 Dashboard: alle KPIs + Global-Benchmark

  • build.py: fetch_op3_data() fetcht zusätzlich:
  • /downloads/show?format=json → paginiert alle Rows, aggregiert nach Datum
  • /queries/top-apps → globale App-Marktanteile → globalApps: {...}
  • app.js: OP3 Dashboard vollständig überarbeitet — alle verfügbaren KPIs:
  • style.css: .op3-bar-row-ext, .op3-bar-glob, .op3-ep-stat-info ergänzt

v7.2.8 – OP3 Dashboard: vollständige KPIs + UX

  • app.js: OP3Stats komplett neu — alle verfügbaren API-KPIs visualisiert:
  • Episode-Card: Gesamt/7d/24h Downloads + Momentum-Badge (Trending/Aktiv/Stabil)
  • Show-KPIs: Downloads/30d, Ø/Woche, Wochen gemessen — je mit ℹ Info-Popover
  • Woche-über-Woche Trend: Pfeil (↑↓→) + % mit Formel-Erklärung
  • Wochen-Sparkline: SVG-Liniendiagramm mit Area-Fill + Datenpunkten
  • Konsistenz-Ring: SVG-Kreisdiagramm (aktive Tage/30), Activity-Heatmap (30 Dots)
  • Apps: Balkendiagramm, V4V-Apps (Fountain etc.) orange hervorgehoben + ⚡ V4V%-Badge
  • app.js: ℹ Info-Button → Popover mit Erklärung + Berechnungsformel,
  • style.css: .op3-tooltip-wrap/.pop, .op3-ep-card, .op3-momentum,

v7.2.7 – OP3 Episode-individuelle Download-Statistiken

  • build.py: fetch_op3_data() fetcht /queries/episode-download-counts
  • build.py: get_episode_meta() extrahiert item_guid aus <guid> Tag
  • build.py: make_hu_config(ep_guid=...) — sucht Episode in op3_data["episodes"],
  • app.js: OP3 Dashboard zeigt "Diese Episode" KPI-Block (24h/7 Tage/Gesamt)

v7.2.6 – OP3 Dashboard ReferenceError Fix

  • app.js: dlData war noch referenziert aus der alten fetch()-Version
  • app.js: catch-Block zeigt jetzt den echten Fehlertext (e.message)

v7.2.5 – OP3 Datenformat-Fix (TSV + Dict)

  • build.py: fetch_op3_data()/downloads/show/ liefert TSV (kein JSON).
  • build.py: top-apps-for-show liefert appDownloads-Dict ({AppName: count}),
  • app.js: OP3 Dashboard liest appData direkt als [[name,count],...] Liste,
  • Echte Daten bestaetigt: 244 Downloads/Monat, 56 Ø/Woche,

v7.2.4 – OP3 showUuid Fix

  • build.py: fetch_op3_data() — Root-Ursache der 400-Fehler gefunden:

v7.2.3 – OP3 API Endpoint Fixes

  • build.py: fetch_op3_data() — drei falsche API-Endpunkte korrigiert:
  • app.js: OP3 Dashboard zeigt jetzt korrekte Felder aus den neuen Endpoints:

v7.2.2 – OP3 Token Security: Build-Zeit statt Browser

  • build.py: fetch_op3_data() neue Funktion – fetcht OP3-Statistiken beim Build
  • build.py: make_hu_config() gibt op3Token nicht mehr aus –
  • app.js: OP3Stats.render() liest aus window.HU_CONFIG.op3Data (statisch,
  • config.py: OP3_TOKEN Key entfernt – Kommentar erklaert warum und wie

v7.2.1 – Versioning, Performance & Docs

  • build.py: get_version() liest jetzt primaer aus CHANGELOG.md (erste ## vX.Y.Z Zeile). Kein Drift mehr zwischen angezeigter Version und tatsaechlichem Stand. version-Datei bleibt fuer abflug.sh Commit-Messages. VERSION_FALLBACK nur noch Notfall-Fallback
  • app.js: passive:true auf window.scroll Listener im Hero-Parallax ergaenzt – fehlte als einziger Scroll-Listener ohne passive-Flag
  • AGENTS.md: Vollstaendig neu geschrieben fuer v7 – Versionierungs-System, Sprint 3+4 Module, window.HU_CONFIG, alle Platzhalter, Anti-Patterns aktualisiert
  • README.md: Aktualisiert auf v7.2.0 – Player-Features Tabelle, OP3-Setup, neues Versioning-System dokumentiert, Cypherpunk Sunrise Design erwaehnt

v7.2.0 – Sprint 3+4: Advanced Player · Settings · OP3 Dashboard · V4V

  • app.js: Playback Speed — 6 Pills (0.5×/0.75×/1×/1.25×/1.5×/2×) im Full-Screen Sheet; gewählte Geschwindigkeit in localStorage gespeichert, bleibt über Sessions erhalten
  • app.js: Sleep Timer — Mond-Button in Mini-Bar + Zeile im Sheet (Off/15/30/45/60 min); Countdown-Badge am Button (z.B. "14m"); pausiert Audio automatisch, Toast-Benachrichtigung
  • app.js: Bookmarks + Timestamp-Share — Bookmark-Button im Sheet speichert aktuelle Position; Lesezeichen-Panel (🔖) mit Liste, Sprung-Funktion, Share-URL (#t=123), Löschen; URL-Hash #t=N beim Laden automatisch erkannt
  • app.js: Settings Panel (⚙️ in Mini-Bar) — 5 Toggles mit localStorage-Persistenz: Auto-Play nächste Folge, Skip Intro (konfigurierbarer Offset in Sekunden), V4V Streaming, Transkript Auto-Scroll, Kapitel-Bilder
  • app.js: V4V WebLN Streaming — Alby/WebLN-Integration; LNURL-Pay Auflösung für Lightning-Adresse; 1 Sat/min (konfigurierbar via config.py → V4V_SATS_PER_MIN); aktives Streaming-Badge in Mini-Bar; graceful fallback wenn keine Extension
  • app.js: OP3 Stats Dashboard — lädt beim ersten Öffnen des Funkraum-Tabs lazy; ohne Token: KPI-Box + Link zu op3.dev; mit OP3_TOKEN in config.py: Downloads-Sparkline (90 Tage, SVG), App-Verteilung als Balkendiagramm, Länder-Verteilung — alles ohne externe Chart-Library (pure SVG)
  • app.js: Auto-Play nächste Folge — nach ended-Event lädt nächste Karte aus DOM-Reihenfolge; Toggle in Settings; kein Autostart wenn deaktiviert
  • build.py: make_hu_config() — generiert <script>window.HU_CONFIG={...}</script> inline auf allen Seiten; überträgt skipIntroOffset, v4vSatsPerMin, op3Token, op3Guid, zapAddress aus config.py zu JavaScript ohne Hardcode
  • config.py: 3 neue Keys in Sektion 18 — SKIP_INTRO_OFFSET (int, Sekunden), OP3_TOKEN (str, leer = public only), V4V_SATS_PER_MIN (int)

v7.1.0 – Kontrast, Bugfixes & UX-Polishing

  • style.css: Kontrast-Fixes überall — Nav-Links von --text-faint (22% opacity) auf --text-dim (50%) angehoben; Tagline gleich; ep-desc auf --text mit 70% opacity statt 50%; ep-btn-like und ep-btn-details ebenfalls lesbarer
  • style.css: .log-entry ullist-style: disc ergänzt, padding-left: 1.2em für Einrückung, ::marker in Sunrise-Orange. Bullets im Logbuch waren durch globalen CSS-Reset unsichtbar
  • style.css: .hangar-link — neue Klasse für "Zum vollständigen Episoden-Archiv" Link; war komplett unstyle (nur plain text). Jetzt Space Mono, Sunrise-Orange, uppercase, hover-Transition
  • style.css: .teaser-box — Flexbox-Layout für Teaser-Card; audio full-width; Zap-Button flush links
  • app.js: Like-Button Bug Fix — setupEpisodeCards() erfasste nur .episode-card, nicht .latest-episode-card (Featured Card). Fix: querySelectorAll(".episode-card, .latest-episode-card") — Like funktioniert jetzt auf allen Karten
  • build.py: ⚡ Lightning-Zap-Button aus eigenem Div herausgelöst und direkt in die Teaser-Box integriert — logisch sinnvoll: V4V-Aktion im Kontext des Teaser-Audios statt schwebend zwischen Sektionen

So liest du diese Roadmap

Sprint-Status

  • Geplant Naechster Sprint, priorisiert
  • In Arbeit Aktueller Sprint, laeuft
  • Abgeschlossen Fertig, eingeklappt lesbar

Prioritaet

  • P0 Kritisch – blockiert den Betrieb
  • P1 Hoch – naechster Sprint
  • P2 Mittel – wenn Zeit bleibt
  • P3 Niedrig – Backlog
  • In Arbeit
  • Erledigt

Kategorien

  • Robustheit Build-Stabilitaet, Error Handling
  • Content & UX Neue Folgen, UI-Verbesserungen
  • Performance Ladezeit, Lighthouse, SW
  • Podcast 2.0 Value4Value, Nostr, Chapters
  • Engine SSG-Kern, Metriken, Infrastruktur

Sprints laufen ~1–2 Wochen. Neueste Sprint oben. Abgeschlossene Sprints sind komprimiert und gedaemmt dargestellt.

Sprint 11 – Folge 8 + PageSpeed 90+ + VolltextsucheGeplant

Geplant · 2026-04-01+

  • P0 Neue Folge 8 aufnehmen — Cover JPG+WebP, SRT, Kapitel-JSON; Template src/shownotes/8_*.md bereit
  • P1 PageSpeed Mobile >= 90 messen (PageSpeed Insights) — Baseline: 78 (13.03.2026); Fixes v9.3.0 deployed, Messung ausstehend
  • P1 NIP-94 bei Folge 8 als erster Live-Test mit neuer Episode
  • P2 castr.me Feed-URL als <podcast:alternateEnclosure> im RSS eintragen
  • P2 Volltextsuche: search_index.json + Fuse.js client-seitig
  • P3 ZapThreads Shadow-DOM-Styling via #ztr-root an Cypherpunk-Sunrise anpassen

Sprint 10 – Nostr-native: Artikel, Kommentare & dezentraler FeedAbgeschlossen

Abgeschlossen · 18.03.2026

Baustein 1 — NIP-23 Shownotes als Nostr-Artikel

  • publish_to_nostr() — 8 Folgen als kind:30023 auf 3 Relays
  • GUID-basierter d-Tag, published_at aus RFC-2822, OP3-Prefix-Stripping
  • _bech32_encode() + _naddr_encode() pure Python stdlib
  • Blossom BUD-01: Cover-Bilder content-addressiert
  • NOSTR_SSG_NAME, NOSTR_EPISODE_LINK_LABEL, NOSTR_HABLA_HINT in config.py

Baustein 2 — ZapThreads + Sub-Tabs

  • Funkraum-Tab: zwei Sub-Tabs 📊 Telemetrie | 💬 Bordfunk
  • _zapthreads_element() mit disable="relayInformation" + NOSTR_SEARCH_RELAYS
  • _habla_hint_html() mit korrekt berechnetem GUID-Slug
  • Bug behoben: Slug-Mismatch zwischen habla-Hint, ZapThreads-anchor, NIP-23 d-Tag
  • Script-Quelle: unpkg.com/zapthreads, defer
  • P3 Shadow-DOM-Styling — Sprint 11

Baustein 3 — castr.me + kind:1 + NIP-94

  • _build_nip1() — immutable Announcements mit MP3-URL für castr.me
  • _build_nip94() — kind:1063 mit SHA256, Größe, Dauer
  • kind:1 Cache-Schutz: nip1:{guid} niemals mit --force überschreiben
  • P2 Feed-URL als <podcast:alternateEnclosure> — Sprint 11

PageSpeed

  • display=optional Bunny Fonts (~700ms LCP)
  • generate_thumbnails() — 120px Thumbnails (~340 KiB gespart)
  • Forced-Reflow-Fixes (setupScrollReveal + setupParallax)
  • aria-label auf DETAILS-Links
  • P1 Messung >= 90 — Sprint 11

Sprint 9 – Value4Value & VolltextsucheGeplant

Geplant · 2026-04-05+

Von Sprint 7 uebernommen

  • P1 Lighthouse Score >= 95 messen (PageSpeed Insights Desktop) — LCP, CLS, INP dokumentieren; Zielwert als KPI in statistiken.html einbauen
  • P1 Neue Folge 8 aufnehmen — Cover JPG+WebP, SRT, Kapitel-JSON; Shownotes-Template src/shownotes/8_*.md bereits fertig

Podcast 2.0: Value4Value

  • build.py: podcast:value pro Item via v4v_recipients YAML (Schema v2.2) — Gast-Split pro Episode; Channel-Level als Fallback
  • P1 app.js: V4V Streaming-Dashboard im Sheet — gesendete Sats, Laufzeit, Empfaenger-Split sichtbar
  • P2 app.js: Boost-Button — einmaliger Zap mit konfigurierbarem Betrag neben Streaming-Toggle
  • P2 app.js: WebLN-Fallback — kein Wallet → QR-Code mit BOLT11-Invoice

Volltextsuche

  • P1 build.py: search_index.json bei jedem Build generieren — Titel, seo_description, Shownotes aller Episoden
  • P1 app.js: Client-seitige Suche via Fuse.js (fuzzy, ~4 kB) oder nativer Filter
  • P2 build.py + style.css: Suchfeld in Nav oder /search.html mit Live-Ergebnissen
  • P2 app.js: Suchergebnis klickbar — Episode direkt spielen oder Seite oeffnen
  • P3 build.py: Transkript-Text in search_index.json aufnehmen (volltext-durchsuchbar)

Sprint 8 – RSS-Generator, Shownotes-Architektur & Feed-QualitätAbgeschlossen

Abgeschlossen · 11.03.2026 · ca. 6h

RSS aus Frontmatter (v8.11.0)

  • build.py: build_feed(version) — generiert feed.xml vollautomatisch aus src/shownotes/*.md; manuelle XML-Bearbeitung entfaellt
  • build.py: parse_frontmatter(text) — YAML-Frontmatter-Parser; bool/int/str Typ-Inferenz; Leerzeilen im FM korrekt behandelt
  • build.py: load_episodes_from_shownotes() — Pflichtfelder-Validierung; ungueltige Eintraege ueberspringen ohne Build-Crash
  • build.py: _date_to_rfc() — ISO-8601 mit Timezone-Offset nach RFC-2822 UTC; v2.0-Format (YYYY-MM-DD) weiterhin unterstuetzt
  • config.py: Sektion 0 — FEED_* Keys fuer Kanal-Metadaten; Lightning V4V; Nostr-Profil
  • config.py: Schema v2.1 — SHOWNOTES_SCHEMA_VERSION als Dokumentations-Key; Sprint-Warnungen bereinigt
  • src/shownotes/: Alle 8 Episoden + Trailer auf Schema v2.1 — schema_version, seo_description, enclosure_type, datum mit Uhrzeit+Timezone
  • src/shownotes/8_*.md: Folge-08-Vorlage — vollstaendiges v2.1-Schema mit Kommentaren; wird uebersprungen bis audio_bytes > 0

Debugging & Code-Qualitaet (v8.11.1)

  • build.py: seo_description Durchstich — build_feed() schreibt als <itunes:summary>; get_episode_meta() liest es als desc_meta fuer HTML <meta name="description">
  • build.py: EN-DASH in f-string entfernt — build_stats() Titel-Konkatenation mit ASCII-Bindestrich
  • docs/CHANGELOG.md: Heading-Level-Bugs # vX.Y.Z## vX.Y.Z korrigiert
  • build.py: HTML5-Bug — meta['title'] mit & ohne html.escape() in <title> und og:title; Fix: html_lib.escape() angewendet
  • build.py: ***bold+italic*** erzeugte ungueltige HTML5-Verschachtelung — Pattern-Reihenfolge korrigiert

Feed-Qualitaet & Code-Hygiene (v8.11.2)

  • build.py: <description> auf 300 Zeichen + Episoden-URL begrenzt
  • build.py: <content:encoded> als Intro-Preview (vor erstem <hr>) + Link → Shownotes & Ressourcen
  • build.py: Relative #-Anker in content:encoded → absolute URLs (W3C Feed Validator)
  • build.py: <a name="..."><span id="..."> (W3C HTML Checker, HTML5-valide)
  • build.py: podcast:value pro Item via v4v_recipients YAML-Feld (Schema v2.2)
  • build.py: Bare except:except (ValueError, TypeError): in _season_sort()
  • build.py: DeprecationWarning utcnow/utcfromtimestamp → timezone-aware (Python 3.14-sicher)
  • version: auf 8.11.2 aktualisiert

Sprint 7 – Engine-Metriken & PerformanceAbgeschlossen

Abgeschlossen · 12.03.2026 · ca. 8h

Engine-Metriken

  • build.py: Git-Metriken beim Build — Commit-Count, Alter, Lines of Code via subprocess__STATS_DATA__.project
  • app.js: Codeberg API Live-Fetch — GET /api/v1/repos/urlaub/pages → Stars, letzter Push
  • app.js: Neue Sektion „Engine-Status" in statistiken.html — COMMITS, LOC, SHA, STARS, BUILD-ZEIT
  • statistiken.html: fetchedAt als „Stand: TT.MM.JJJJ HH:MM" im Dashboard-Header

Performance

  • build.py: generate_critical_css() — extrahiert und minifiziert :root, Reset, html/body, Nav, Hero, Home-Header inline in <head>; style.css non-blocking via media="print" onload; <noscript>-Fallback
  • sw.js: stale-while-revalidate fuer episode/*.html
  • app.js: requestIdleCallback fuer ScrollReveal, Ripple, TranscriptScroll; Parallax synchron (LCP-kritisch)
  • Lazy loading: Hero-Bilder fetchpriority="high" ohne loading, neueste Episode loading="eager", alle anderen loading="lazy" — bereits korrekt
  • Lighthouse Score >= 95 — Desktop-Browser fuer PageSpeed nicht verfuegbar; verschoben → Sprint 9

Content

  • navigator.share() Web Share API Level 2 mit Episodenbild — Fallback: Basic Share → Clipboard
  • build.py: seo_description als <itunes:summary> aktiv
  • Neue Folge 8 — Folge 7 erst kuerzlich veroeffentlicht; verschoben → Sprint 9

Sprint 6 – Radarbild, Robustheit, Hangar-RedesignAbgeschlossen

Abgeschlossen · 2026-03-07 · 10h

Robustheit & Bugfixes

  • build.py + app.js: Doppelter Play-Handler entfernt (v7.6.7 Root-Cause Fix)
  • app.js: Player.inject() Guard, immer bedingungslos aufgerufen (v7.6.6)
  • app.js: Transcript IIFE-Scope-Bug — state/aud auf Player.getState()/Player.getAudio() (v7.6.4)
  • build.py: fetch_op3_data() graceful degradation, OP3-Cache (.op3_cache.json)
  • abflug.sh: set -e — kein Push bei fehlgeschlagenem Build

Hangar-Redesign

  • build.py: build_archive() komplett neu — gleichwertige Cards, Staffel-Tabs, kein Hero
  • build.py: Tab-Labels aus config.py statt Hardcode

Radarbild

  • statistiken.html: 9 Sektionen, Episode-KPIs im Funkraum-Tab

Sprint 5 – Mega-Dashboard, W3C, Repo-HygieneAbgeschlossen

Abgeschlossen · 2026-02-28 · 8h

  • W3C Nu Html Checker: 0 Errors auf allen Seiten
  • Nav vereinfacht, Footer auf allen Seiten konsistent
  • Vollstaendige CSS-Trennung: 0 inline style=, 0 cssText
  • .gitignore, AGENTS.md, README.md aktualisiert

Sprint 3–4 – OP3, Versionierung, Advanced PlayerAbgeschlossen

Abgeschlossen · 2026-02-20 · 6h

  • OP3 Stats: Show-Downloads, App-Mix, Globaler Benchmark-Vergleich
  • window.HU_CONFIG als zentrale Datenschnittstelle aller Seiten
  • Bookmarks, AutoPlay, Sleep Timer, V4V WebLN, SettingsPanel (5 Toggles)
  • CHANGELOG.md als Single Source of Truth fuer Versionierung

Sprint 1–2 – Player, Podcast 2.0, Chapters, TranscriptAbgeschlossen

Abgeschlossen · 2026-01-15 · 5h

  • Bottom Player: Mini-Bar + Full-Screen Sheet, 6 Geschwindigkeitsstufen
  • Podcast 2.0: Chapters (JSON, klickbar, Cover-Images), Transcript (SRT→HTML, Auto-Scroll)
  • Nostr ZapThreads (NIP-25), Lightning Zap Modal

Sprint 0 – GrundgeruestAbgeschlossen

Abgeschlossen · 2025-11-04 · 3h

  • Static Site Generator Python (kein Framework, kein npm)
  • feed.xml als Single Source of Truth
  • Codeberg Pages Deploy via abflug.sh
  • Cypherpunk Sunrise Design-System (Dark Mode Only)
  • PWA: sw.js + manifest.json BUILD-GENERIERT

So liest du diesen Issues-Tracker

Bekannte Bugs und offene Baustellen. Jeder Eintrag hat einen Schweregrad, einen Status und — wenn bekannt — einen konkreten Fix-Hinweis.

Schweregrad

  • S0 Kritisch – blockiert den Betrieb
  • S1 Hoch – deutlich sichtbarer Fehler
  • S2 Mittel – Workaround vorhanden
  • S3 Niedrig – kosmetisch / Edge Case

Status

  • OFFEN Noch nicht angegangen
  • IN ARBEIT Fix in Entwicklung
  • BLOCKIERT Wartet auf externe Abhängigkeit
  • BEHOBEN Deployed, zum Löschen vorgemerkt

Behobene Issues bleiben sichtbar bis zum naechsten grossen Release-Cleanup. So bleibt die Bug-Geschichte nachvollziehbar.

Aktuelle Issues

*Keine offenen Issues — alle bekannten Bugs behoben per v9.3.0.*

Behobene Issues (Sprint 10)

S1 ZapThreads zeigt keine Kommentare — NIP-11 Relay-FilterungBEHOBEN

Status: BEHOBEN v9.3.0

Seit: v9.1.0

Beschreibung: ZapThreads verwirft Relays wegen fehlerhafter NIP-11-Antworten bevor es nach Events sucht. Kombination mit fehlendem Index-Relay: kind:1 Announcements auf nos.lol/damus.io/snort.social werden nicht relay-übergreifend gesucht.

Fix: disable="relayInformation" auf <zap-threads> deaktiviert NIP-11-Filterung. NOSTR_SEARCH_RELAYS: ["wss://relay.nostr.band"] in config.py — indexierendes Relay das Events schnell relay-übergreifend findet.

S1 Slug-Mismatch: habla-Link und ZapThreads zeigten auf falsches NIP-23 EventBEHOBEN

Status: BEHOBEN v9.3.0

Seit: v9.1.0

Beschreibung: _habla_hint_html() und _zapthreads_element() verwendeten meta["slug"] (URL-basiert: folge-7-ki-souveraenitaet-...). _build_nip23() generiert NIP-23 d-Tag aber aus GUID (folge-007-urlaub). Habla-Links und ZapThreads-anchor verwiesen auf nicht-existente Events.

Fix: Beide Funktionen berechnen Slug aus GUID: re.sub(r"[^a-z0-9-]", "-", guid.lower())[:60].

S1 kind:1 Announcements würden bei --force dupliziertBEHOBEN

Status: BEHOBEN v9.3.0

Seit: v9.3.0 (bei Implementierung erkannt)

Beschreibung: kind:1 ist auf Nostr immutabel. --force würde neue kind:1 erzeugen → Spam auf dem Profil.

Fix: Cache-Key nip1:{guid} wird niemals durch --force überschrieben.

S1 publish_to_nostr erhielt rohe Frontmatter-DictsBEHOBEN

Status: BEHOBEN v9.3.0

Seit: v9.1.0

Beschreibung: __main__ übergab load_episodes_from_shownotes() direkt. audio_url mit OP3-Prefix, relative cover_url, kein pub_date_str. Alle published_at-Timestamps waren time.time() → gleich → falsche Reihenfolge im Profil.

Fix: make_meta_from_ep() wird vor publish_to_nostr() angewendet.

S1 ZapThreads Custom Element nie gerendert — falscher ContainerBEHOBEN

Status: BEHOBEN v9.3.0

Seit: v8.0.0

Beschreibung: <div class="zapthreads-container" data-uri="..."> — ZapThreads ignoriert Attribute auf normalem div.

Fix: <zap-threads anchor="naddr1..." relays="..." user="npub1..." disable="relayInformation">.

S2 ZapThreads Script von totem CDN (ERR_CONNECTION_FAILED)BEHOBEN

Status: BEHOBEN v9.3.0

Seit: unbekannt

Beschreibung: zapthreads.dev war laut PageSpeed offline. async statt defer → Custom Element evtl. undefiniert beim Parsen.

Fix: unpkg.com/zapthreads/dist/zapthreads.iife.js, defer.

S2 Habla-URL doppelter PrefixBEHOBEN

Status: BEHOBEN v9.3.0

Seit: v9.3.0 (bei Implementierung erkannt)

Beschreibung: [naddr] wurde durch https://habla.news/a/{naddr} ersetzt, aber NOSTR_HABLA_HINT enthielt bereits https://habla.news/a/[naddr].

Fix: [naddr] → vollständige URL im href, Link-Text "habla.news".

S2 Forced Reflow in setupScrollReveal (55ms)BEHOBEN

Status: BEHOBEN v9.3.0

Seit: v7.0.0

Beschreibung: getBoundingClientRect() innerhalb forEach die auch classList.add() setzt → Layout-Thrashing.

Fix: Alle Reads vor Writes gebündelt.

S2 Forced Reflow in setupParallax (55ms)BEHOBEN

Status: BEHOBEN v9.3.0

Seit: v7.0.0

Beschreibung: img.offsetHeight synchron vor erstem Paint gelesen.

Fix: requestAnimationFrame(() => { h = img.offsetHeight; }).

S2 ep-cover Thumbnails: ~340 KiB unnötigBEHOBEN

Status: BEHOBEN v9.3.0

Seit: v7.0.0

Beschreibung: 670–744px Bilder für 58px Anzeige.

Fix: generate_thumbnails() erzeugt 120px JPEG+WebP; srcset in <picture>.

S2 Bunny Fonts render-blocking (760ms)BEHOBEN

Status: BEHOBEN v9.3.0

Seit: v7.0.0

Beschreibung: display=swap blockiert Rendering bis Schrift geladen.

Fix: display=optional.

S2 DETAILS-Links nicht unterscheidbar für ScreenreaderBEHOBEN

Status: BEHOBEN v9.3.0

Seit: v7.0.0

Beschreibung: 8× "Details →" ohne Kontext.

Fix: aria-label="{title} – Details".

S2 bech32-Decode: Version-Byte-Bug (31 statt 32 Bytes)BEHOBEN

Status: BEHOBEN v9.1.0

Seit: v9.1.0

Beschreibung: bytes(out[1:]) (Segwit) statt bytes(out) (Nostr) → nsec abgelehnt.

Fix: bytes(out).

S2 `style.css` blockiert Rendering — kein Critical-CSS-PfadBEHOBEN

Status: BEHOBEN v8.13.0

Seit: v1.0.0

Beschreibung: <link rel="stylesheet" href="style.css"> im <head> ist render-blocking: Browser rendert keine einzige Pixel bevor style.css vollständig geladen und geparst ist. Bei langsamen Verbindungen (3G, Café-WLAN) bedeutet das mehrere Sekunden weißer/schwarzer Bildschirm — FCP und LCP leiden direkt. 95KB CSS für einen Above-the-Fold-Bereich der nur ~3KB braucht.

Ursache: Kein Critical-CSS-Mechanismus vorhanden. Standard-Pattern für statische Sites, aber messbar suboptimal.

Fix: generate_critical_css() extrahiert 20 kritische Selektoren aus style.css, minifiziert auf ~3KB, bettet sie inline in <head> ein. style.css wird non-blocking geladen (media="print" onload). <noscript>-Fallback für JS-disabled. Browser kann sofort rendern — Hero, Nav, Typografie erscheinen ohne Netzwerkwartezeit.

S3 `twitter:image`, `twitter:title`, `twitter:description` fehlten in `make_head()`BEHOBEN

Status: BEHOBEN v8.13.0

Seit: v1.0.0

Beschreibung: make_head() hatte twitter:card aber keine weiteren Twitter/X-Tags. Nostr-Clients (Amethyst, Primal), Mastodon und ältere Twitter-Clients nutzen twitter: Tags für Link-Previews — nicht ausschließlich og:. Episodenlinks auf Nostr zeigten kein Vorschaubild in allen Clients.

Fix: twitter:image, twitter:title, twitter:description mit denselben Werten wie og: in make_head() ergänzt. Gilt jetzt für Index, Archiv, Changelog, Statistiken. Template-Seiten (Episodenseiten) bereits in v8.12.5 gefixed.

S2 `.card { overflow: hidden }` blockiert horizontalen Table-ScrollBEHOBEN

Status: BEHOBEN v8.12.5

Seit: v7.0.0

Beschreibung: Tabellen in Shownotes (.table-scroll { overflow-x: auto }) scrollten auf Mobile nicht, obwohl der Wrapper korrekt gesetzt war. Ursache: .card hatte overflow: hidden, was alle Kinder-Scroll-Container clippt — overflow-x: auto auf einem Kind wird durch overflow: hidden auf dem Elternelement faktisch deaktiviert.

Warum vorher kein Problem: Tabellen wurden erst ab Folge 7 eingesetzt. Auf Desktop hat der Viewport genug Breite, das Problem tritt nur auf Mobile auf.

Fix: .card { overflow: clip }clip schneidet visuellen Overflow ab (Noise-Texture-Pseudo-Element, Box-Shadow bleiben korrekt), erzeugt aber keinen Scroll-Container und blockiert keine Kind-Scroll-Elemente. overflow: hidden erzeugt implizit einen neuen Block Formatting Context; overflow: clip nicht.

S2 Share-Button übergibt kein Episodenbild — Android zeigt Podcast-CoverBEHOBEN

Status: BEHOBEN v8.12.5

Seit: v8.12.0

Beschreibung: navigator.share({ title, url }) ohne files[]-Parameter: Android Chrome zeigt im Share-Sheet eine Thumbnail basierend auf dem aktuellen Tab-Kontext (oft der Index-Hero = Podcast-Cover), nicht auf dem og:image der geteilten Episode-URL. RSS-Reader und Messenger zeigen beim Link-Preview das richtige Episodenbild (aus og:image), aber der native Share-Dialog selbst nicht.

Fix: Web Share API Level 2 in template.html: Episodenbild via fetch() holen, als File-Objekt übergeben ({ files: [file] }), navigator.canShare() prüfen. data-share-img Attribut in build.py ergänzt. Dreistufige Fallback-Kette: Level-2-Share → Basic-Share → Clipboard.

S3 `twitter:image` fehlte im Template — Social-Previews unvollständigBEHOBEN

Status: BEHOBEN v8.12.5

Seit: v1.0.0

Beschreibung: template.html hatte twitter:card aber kein twitter:image, twitter:title, twitter:description. Twitter/X, Mastodon und Nostr-Clients nutzen Twitter Card Tags für Link-Previews, nicht ausschließlich og:. Episodenlinks auf Nostr zeigten kein Vorschaubild.

Fix: twitter:image, twitter:title, twitter:description mit denselben {{META_IMG}}/{{PAGE_TITLE}}/{{META_DESC}} Platzhaltern ergänzt.

S2 WCAG 2.1 AA: Labels unter 0.5rem — unter jedem Kontrast nicht lesbarBEHOBEN

Status: BEHOBEN v8.12.3–v8.12.4

Seit: v7.0.0

Beschreibung: op3-kpi-label 0.48rem, werft-kpi-label 0.44rem, kpi-card-label 0.44rem, kpi-stat-label 0.40rem, eng-kpi-label 0.42rem (≈ 6–7px). Unterhalb von 9px ist Uppercase-Mono-Text für Menschen mit durchschnittlicher Sehschärfe auf einem OLED-Display faktisch nicht lesbar, unabhängig vom Kontrastverhältnis.

Fix: Alle Labels auf ≥ 0.62rem (≈ 9.9px) angehoben. --text-dim Opacity 0.50 → 0.65 für WCAG-AA auf --surface-2 (war 4.48:1, jetzt 6.71:1).

S2 8 Inline-Styles in `app.js` — Styling außerhalb CSSBEHOBEN

Status: BEHOBEN v8.12.4

Seit: v7.0.0

Beschreibung: style=-Attribute direkt in innerHTML-Strings: Toast-Titel, Toast-Text, Skip-Icons, Like-Overlay-Buttons, Settings-Groups, OP3-Fehlermeldung, Snapshot-Zeitstempel, Werft-Section-Titel. Verstoß gegen die Trennung von Verhalten (JS) und Darstellung (CSS). Macht Style-Änderungen fehleranfällig (zwei Quellen der Wahrheit).

Fix: Alle 8 Stellen durch CSS-Klassen ersetzt. Einzig verbliebener Inline-Style: bp-transcript-scroll font-size (User-Preference zur Laufzeit, nicht in CSS ausdrückbar), kommentiert.

Status: BEHOBEN v8.12.1 (abflug.sh v7.6.0)

Seit: v7.0.0

Beschreibung: git stash pop wurde mit > /dev/null 2>&1 aufgerufen. Bei einem Merge-Konflikt zwischen lokalen Änderungen und dem Codeberg-Stand schlug stash pop kommentarlos fehl — die lokal geänderten Quelldateien wurden durch die älteren Codeberg-Versionen ersetzt. Der Build lief anschließend durch, pushte aber die alten Dateien. Keine Fehlermeldung, kein sichtbares Signal.

Ursache: Stash/pop ist ein zweiphasiges Muster mit einem unsichtbaren Fehlermodus: git stash speichert lokale Änderungen, git pull lädt Codeberg-Stand (der für dieselben Dateien andere Inhalte hat), git stash pop kann den Conflict nicht auflösen und gibt still auf. Weil beide Ausgaben nach /dev/null umgeleitet wurden, war kein Hinweis sichtbar.

Warum vorher kein Problem: Im normalen Workflow werden Dateien direkt im Repo bearbeitet und sofort per abflug.sh deployed. Codeberg und lokal sind vor dem Edit synchron — stash pop trifft auf keine Konflikte. Das Muster brach erst auf, als Dateien von einer externen Quelle (KI-Session) geliefert und ins Repo kopiert wurden, während Codeberg noch den alten Stand hielt. In diesem Fall unterscheiden sich die Stash-Inhalte vom Pull-Ergebnis — genau das löst den stillen Fehlerpfad aus.

Fix: abflug.sh v7.6.0: git stash/stash pop vollständig entfernt. Neue Reihenfolge: lokale Änderungen per git add . && git commit sichern, dann git pull --rebase. rebase ist deterministisch — bei echtem Konflikt bricht es laut ab (git rebase --abort zum Reset). Kein > /dev/null auf kritischen Operationen.

S2 En-Dash / Pfeil-Zeichen in Python f-strings — Termux SyntaxError-RisikoBEHOBEN

Status: BEHOBEN v8.12.1

Seit: v8.11.0

Beschreibung: build.py L1156 enthielt "—" (U+2014 EM-Dash) und L2090 "→" (U+2192 Pfeil) in echten f-string-Ausdrücken (keine print-Statements). Python 3.12 auf Termux kann bei bestimmten Encoding-Konfigurationen mit Unicode-Literalen in f-strings einen SyntaxError werfen.

Ursache: Anti-Pattern aus AGENTS.md war in neu geschriebenem Code versehentlich eingebaut worden. Der automatisierte Check prüfte nur print()-Zeilen, nicht alle f-string-Nodes.

Fix: L1156: String-Konkatenation mit ASCII "-"; L2090: " -> " statt " → ". Regex-Check verschärft: jetzt alle ast.JoinedStr-Nodes geprüft.

S2 `overflow: hidden` auf `.hero-section` — Gradient-Blend unterbrochenBEHOBEN

Status: BEHOBEN v8.12.1

Seit: unbekannt (mindestens v8.9.0)

Beschreibung: style.css L131 hatte overflow: hidden auf .hero-section, obwohl dies seit v7.0.0 als Anti-Pattern in AGENTS.md dokumentiert ist. Das ::after-Overlay mit linear-gradient zum Seitenhintergrund wurde hart abgeschnitten statt weich auszublenden.

Ursache: Regel wurde bei einem Refactoring wieder eingebaut ohne AGENTS.md-Check.

Fix: overflow: hidden entfernt. Nur position: relative; width: 100% bleibt.

S2 22 `<button>` ohne `type="button"` in dynamisch generiertem HTML (app.js)BEHOBEN

Status: BEHOBEN v8.12.1

Seit: v7.0.0

Beschreibung: Bottom-Player-Toolbar (Mini + Sheet), Zap-Modal, Like-Overlay und Bookmark-Buttons wurden per innerHTML ohne type="button" erzeugt. Standard-Typ ist type="submit" — in einem Form-Kontext würde jeder Klick ein Form-Submit auslösen. HTML5-invalid.

Ursache: type="button" fehlt erfahrungsgemäß in dynamisch erzeugtem HTML, weil es kein Lint-Tool automatisch bemängelt.

Fix: Alle 22 Stellen per Regex ergänzt. Sonderfall: Button in einem Template-String innerhalb eines JS-String-Literals — doppelte Anführungszeichen im type-Attribut erzeugten einen JS-Syntaxfehler; fix: type='button' mit einfachen Anführungszeichen im String-Kontext.

S2 Relative Anker-URLs in content:encodedBEHOBEN

Status: BEHOBEN v8.11.2

Seit: v8.11.0

Beschreibung: href="#lifehacks-details" in <content:encoded> CDATA war relativ. RSS-Reader kennen keine Basis-URL — Links ins Leere. W3C Feed Validator meldete 6 Vorkommen.

Ursache: parse_shownotes_md() gibt Markdown-Links 1:1 aus; im Feed-Kontext fehlt die Seiten-URL als Basis.

Fix: Nach intro_html-Extraktion: re.sub(r'href="#([^"]+)"', lambda m: f'href="{ep_url_plain}#{m.group(1)}"', intro_html).

S2 `<a name="..."></a>` — HTML5-invalidBEHOBEN

Status: BEHOBEN v8.11.2

Seit: v8.11.0

Beschreibung: W3C HTML Checker: 3 Fehler pro Episodenseite — name-Attribut obsolet, href fehlt, Element ungültig.

Ursache: parse_shownotes_md() reichte <a name="..."></a> direkt durch (war Anti-Escape-Workaround).

Fix: Pattern-Match in Shownotes-Parser ersetzt Anker durch <span id="..."></span> — valide, unsichtbar, gleiche Funktion.

S3 Bare `except:` in `_season_sort()`BEHOBEN

Status: BEHOBEN v8.11.2

Seit: v8.7.1

Beschreibung: try: return int(k) / except: return 9999 fing alle Exceptions inkl. SystemExit und KeyboardInterrupt.

Fix: except (ValueError, TypeError): return 9999.

S3 `DeprecationWarning`: `datetime.utcnow()` und `utcfromtimestamp()`BEHOBEN

Status: BEHOBEN v8.11.2

Seit: v8.11.0

Beschreibung: Python 3.12+ gibt bei jedem Build zwei DeprecationWarning aus. In Python 3.14 werden diese zu Fehlern.

Fix: utcnow()datetime.now(tz=timezone.utc); utcfromtimestamp(t)datetime.fromtimestamp(t, tz=timezone.utc); timezone zu Imports hinzugefügt.

S3 `version`-Datei seit Sessionen veraltetBEHOBEN

Status: BEHOBEN v8.11.2

Seit: v8.10.1

Beschreibung: version-Datei zeigte 8.10.1 während der aktive Build bereits 8.11.x war. Kein Build-Fehler, aber irreführend beim manuellen Check.

Fix: version8.11.2.

S2 seo_description war totes Feld -- kein Durchstich in HTML-MetaBEHOBEN

Status: BEHOBEN v8.11.1

Seit: v8.11.0

Beschreibung: Frontmatter-Schema v2.1 definiert seo_description als SEO-optimierte Kurzbeschreibung (max. 160 Zeichen). Das Feld wurde jedoch an keiner Stelle konsumiert: build_feed() schrieb es nicht in den RSS-Feed, build_episodes() / get_episode_meta() lasen es nicht. Ergebnis: HTML <meta name="description"> verwendete immer sanitize_meta(desc_raw) (= RSS <description> = summary), die selbst bei 3-4 Saetzen deutlich laenger als 160 Zeichen ist und keinen SEO-Fokus hat.

Ursache: Architekturentscheid war unklar dokumentiert ("ist fuer HTML-Meta") -- der Durchstich-Pfad Frontmatter -> RSS -> HTML fehlte.

Fix: build_feed() schreibt seo_description als <itunes:summary>; get_episode_meta() liest itunes:summary bevorzugt als desc_meta; Fallback auf sanitize_meta(desc_raw) wenn Feld leer.

S3 EN-DASH in f-string -- Termux/Android SyntaxError-RisikoBEHOBEN

Status: BEHOBEN v8.11.1

Seit: v8.11.0

Beschreibung: build_stats() enthielt f"{CONFIG['PODCAST_NAME']} - Statistiken" mit U+2013 EN-DASH im f-string-Literal. AGENTS.md Konvention: keine En-Dashes in Python f-strings (Python 3.12+ auf Android/Termux: moeglicher SyntaxError durch konfusable Unicode-Zeichen im Parser). Auf Desktop-Python kein Symptom, erst auf Termux sichtbar.

Ursache: Schreibfehler -- Gedankenstrich statt ASCII-Bindestrich oder Konkatenation.

Fix: f-string durch einfache Konkatenation ersetzt: CONFIG['PODCAST_NAME'] + " - Statistiken".

S3 CHANGELOG.md: v8.10.1 und v8.11.0 mit falschem Heading-LevelBEHOBEN

Status: BEHOBEN v8.11.1

Seit: v8.10.1

Beschreibung: Die Eintrage fuer v8.10.1 und v8.11.0 in docs/CHANGELOG.md verwendeten # vX.Y.Z (H1) statt ## vX.Y.Z (H2). get_version() und parse_changelog_md() erwarten das Format ^## vX.Y.Z per Regex. Direkte Folge: get_version() fand die Versionen nicht als erste Treffer, Anzeige auf changelog.html konnte falsch sortieren.

Ursache: Tippfehler beim manuellen Einfuegen der Eintraege in der KI-Session.

Fix: Beide Eintraege auf ## vX.Y.Z korrigiert; alle bestehenden Eintraege geprueft.

S2 HTML5-Fehler: `&` in `<title>` und `og:title` nicht escapedBEHOBEN

Status: BEHOBEN v8.11.0

Seit: v1.0.0

Beschreibung: Episodentitel mit & (z.B. „Folge 1: Bitcoin ist Komorebi | Nostr & digitale Souveränität") wurden ohne html.escape() direkt in {{PAGE_TITLE}} eingesetzt. In <title> und <meta property="og:title"> erzeugte das & ohne Entity-Kodierung einen W3C Nu Html Checker-Fehler: *"& did not start a character reference"*.

Ursache: meta['title'] kommt aus XML-Parsing (dort korrekt dekodiert), wurde aber beim Einsetzen in den HTML-Template-String nicht erneut escaped.

Fix: html_lib.escape(meta['title']) und html_lib.escape(CONFIG['PODCAST_NAME']) in build_episodes() L2204.

Betroffene Seiten: Alle Episodenseiten mit & im Titel (Folgen 1, 2, 6, 7).

S2 `***fett+kursiv***` erzeugte ungültige HTML5-VerschachtelungBEHOBEN

Status: BEHOBEN v8.11.0

Seit: v8.7.1

Beschreibung: parse_shownotes_md()inline(): ***text*** wurde als <strong><em>text</strong></em> gerendert statt <strong><em>text</em></strong>. Ungültige Verschachtelung nach HTML5-Spec: Block-Level-Tag-Hierarchie verletzt, Browser-Rendering-Artefakte möglich.

Ursache: **-Pattern wurde vor ***-Pattern geprüft. **text** matchte das äußere **, das innere * wurde als separates <em> behandelt und nach </strong> geschlossen.

Fix: ***-Pattern explizit vor **-Pattern in inline() eingefügt: re.sub(r'\*\*\*(.+?)\*\*\*', r'<strong><em></em></strong>', text).

Hinweis: Kein aktueller Shownotes-Text nutzt ***, aber das Muster ist Teil des unterstützten Markdown-Subsets.

S2 `int()` auf Frontmatter-Werte ohne FehlerbehandlungBEHOBEN

Status: BEHOBEN v8.11.0

Seit: v8.11.0-rc

Beschreibung: build_feed() rief int(ep.get("audio_bytes", 0)) und int(ep.get("audio_dauer", 0)) ohne try/except auf. Wäre ein Frontmatter-Wert kein gültiger Integer (z.B. 1234.5 oder "" durch Tippfehler), crashte der gesamte Build mit ValueError.

Ursache: parse_frontmatter() parst reine Ganzzahlen korrekt als int, liefert aber bei gemischten Werten (z.B. 1234.5) einen str. int("1234.5") wirft ValueError.

Fix: Beide int()-Aufrufe in try/except (ValueError, TypeError) gewrappt, Fallback 0.

S0 style.css im falschen Verzeichnis — Font nie geladenBEHOBEN

Status: BEHOBEN v8.9.1

Seit: v8.9.0

Beschreibung: Das aktualisierte CSS (Plus Jakarta Sans, Font-Tokens, tabular-nums) landete beim Kopieren in assets/js/style.css statt assets/css/style.css. Browser lud immer noch den alten v7.8.0-Stand mit Outfit. Keine sichtbare Änderung trotz korrektem HTML-Link /assets/css/style.css?v=....

Ursache: Manueller Kopierfehler bei der Dateiablage.

Fix: Korrekten Stand nach assets/css/style.css verschoben, Stray-File in assets/js/ entfernt.

S1 build.py make_head() — Outfit-Font hardcoded, nicht von template.html überschreibbarBEHOBEN

Status: BEHOBEN v8.9.1

Seit: v8.9.0

Beschreibung: make_head() in build.py hatte die Bunny-CDN-URL mit outfit:300,400,500,600,700 hardcoded. Alle via make_head() generierten Seiten (index.html, changelog.html, statistiken.html, archive.html) luden immer Outfit — egal was in template.html stand. Nur Episodenseiten, die template.html direkt nutzen, hätten Plus Jakarta Sans bekommen.

Fix: make_head() Bunny-CDN-URL auf plus-jakarta-sans:300,400,500,600,700 aktualisiert.

S2 Bookmark-Icon zu klein — optisch aus der ReiheBEHOBEN

Status: BEHOBEN v8.10.0

Seit: v7.2.0

Beschreibung: Das 🔖-Icon im Player-Sheet war sichtbar kleiner als die Icons der anderen Action-Buttons (♡ Like, ⚡ Zap, ↗ Teilen, ⚙️ Settings). Text-Label "Bookmark" stand nicht auf gleicher Höhe. Ursache: .bp-bm-icon hatte keine font-size — erbte 0.56rem vom Button statt 1.1rem wie alle anderen Icons.

Ursache: Der Bookmark-Button wird dynamisch via document.createElement angehängt (nach DOM-Ready), die entsprechende CSS-Klasse hatte keine font-size explizit gesetzt.

Fix: .bp-bm-icon { font-size: 1.1rem; display: block; } ergänzt.

S2 CO-PILOT-Sektion klebt am CTA-BlockBEHOBEN

Status: BEHOBEN v8.10.0

Seit: v8.0.0

Beschreibung: Die Sektion "CO-PILOT — KI-ASSISTENZ" im Werft-Tab hatte keinen sichtbaren Abstand zum darüber liegenden "Hangar öffnen"-CTA-Block inklusive "Quellcode, Issues und Commit-Historie — öffentlich einsehbar." Die beiden Blöcke klebten direkt aneinander.

Ursache: .werft-cta hatte nur margin-top aber kein margin-bottom. Die CO-PILOT-Sektion wird von einem separaten IIFE (KpiPanels) nach dem Haupt-render() angehängt — der Abstand lag ausschließlich beim CTA.

Fix: margin-bottom: 56px auf .werft-cta ergänzt.

S1 Kapitelbilder-Toggle ohne WirkungBEHOBEN

Status: BEHOBEN v8.10.0

Seit: v7.2.0

Beschreibung: Das Setting "Kapitel-Bilder" in den Player-Einstellungen hatte keinerlei Effekt. Kapitelbilder wurden immer angezeigt, egal ob der Toggle an oder aus war.

Ursache: Zwei Bugs gleichzeitig: (1) renderSheetChaps() las SettingsStore.get('chapterImages') nie ab — der Wert wurde gespeichert aber nie konsumiert. (2) onSettingChange('chapterImages') enthielt nur /* applied on next chapter load */ — Änderung wirkte also erst nach manuellem Track-Wechsel.

Fix: renderSheetChaps() prüft SettingsStore.get('chapterImages') !== false vor dem Rendern von Bildern. onSettingChange ruft bei chapterImages sofort renderSheetChaps() auf.

S2 Shownotes-Tabellen auf dunklem Hintergrund nicht lesbarBEHOBEN

Status: BEHOBEN v8.9.0

Seit: v8.7.1

Beschreibung: Markdown-Tabellen in Shownotes (shownotes-table) hatten kaum sichtbaren Kontrast zum Seitenhintergrund. Header-Zeile nur minimal abgehoben, keine Zeilen-Trennung erkennbar, kein Zebra-Muster. Für Inhalte wie die Cloud-vs-Lokal-Kalkulation (mit Zahlen und Vergleichsspalten) praktisch unlesbar.

Fix: Kompletter Tabellen-Stil: Zebra-Muster (odd/even), border-warm-Außenrahmen, Space-Mono-Header in dawn-amber, Hover-State, explizites vertical-align: top.

S2 Kein vertikaler Rhythmus — Sektionen zu dichtBEHOBEN

Status: BEHOBEN v8.9.0

Seit: v7.0.0

Beschreibung: Abstände zwischen Haupt-Sektionen zu gering. Besonders CO-PILOT-Sektion im Werft-Tab klebte am vorherigen Block, Logbuch-Einträge flossen ohne Luft ineinander, KPI-Akkordeon-Panel klebte am nächsten Content.

Fix: .section-divider 22px→44px, .werft-section 32px→52px, .log-entry 24px→36px, .changelog-section 14px→28px, .kpi-accordion 24px→40px.

S2 Zifferndarstellung in KPI-Cards inkonsistent (proportionale Ziffern)BEHOBEN

Status: BEHOBEN v8.9.0

Seit: v7.0.0

Beschreibung: Zahlen in KPI-Cards (Sprint-Metriken, Werft-KPIs, OP3-Stats) nutzten proportionale Mediävalziffern. Bei Werten unterschiedlicher Länge (z.B. "15" vs "174") unterschiedliche optische Gewichtung, Ziffern "sprangen" auf ihrer Baseline.

Ursache: Inter/Outfit/Plus Jakarta Sans haben OpenType-Features für tabular-nums — ohne explizites Aktivieren werden proportionale Ziffern gerendert.

Fix: font-variant-numeric: tabular-nums lining-nums; font-feature-settings: "tnum" 1, "lnum" 1 auf alle KPI/Metric-Selektoren.

S2 Font-Tokens fehlten — Typeface-Änderung erforderte 20+ StellenBEHOBEN

Status: BEHOBEN v8.9.0

Seit: v7.0.0

Beschreibung: Über 160 font-family-Inline-Strings im CSS verteilt ('Outfit', 'Syne', 'Space Mono'). Jeder Font-Wechsel erforderte globales Search & Replace mit Risiko von Regressions. Kein Single Point of Control.

Fix: :root-Tokens --font-body, --font-display, --font-mono eingeführt. Alle 160+ Inline-Strings auf var(--font-*) umgestellt. Änderung jetzt an 3 Stellen: :root, build.py make_head(), template.html.

S2 Body-Font Outfit — suboptimale Ziffernoptik für KPI-ContentBEHOBEN

Status: BEHOBEN v8.9.0

Seit: v7.0.0

Beschreibung: Outfit hatte schwaches optisches Gewicht bei Zahlen und eine niedrigere x-Höhe als ideal für kleine Schriftgrößen (0.7–0.9rem). Bei KPI-Labels und Datenwerten auf kleinen Screens suboptimale Lesbarkeit.

Fix: Outfit → Plus Jakarta Sans (schärfere x-Höhe, ausgeglichenere Ziffernformen, Awwwards-kompatibler Stack mit Syne).

S1 Tab-Bug — `[hidden]`-Attribut überschreibt alle JS-Toggle-StrategienBEHOBEN

Status: BEHOBEN v8.7.1 / v8.1.0 / v7.9.0

Seit: v7.4.0

Beschreibung: Logbuch-Tabs schalteten nicht korrekt um. Root-Cause: CSS-Regel [hidden] { display: none !important } schlägt laut CSS-Kaskade auch style.display und style.cssText-Inline-Styles. Mehrfach als "behoben" markiert, dann regressiert (v7.9.0 → v8.1.0 → v8.7.1). Letzte Manifestation: hidden-Attribut direkt im generierten Tab-Panel-HTML.

Fix (endgültig v8.1.0): Neues Tab-System ausschließlich mit .is-active-Klasse. Kein hidden-Attribut mehr im HTML, kein style.display. [hidden] { display: none !important } nur noch für semantisch-HTML-hidden, nicht für JS-Toggle.

S1 parse_roadmap_md() — letztes Sprint-Div nie geschlossenBEHOBEN

Status: BEHOBEN v8.2.1

Seit: v8.0.0

Beschreibung: Das letzte Sprint-<div> in parse_roadmap_md() wurde nur durch ein abschließendes --- geschlossen. Da ROADMAP.md keinen --- nach dem letzten Sprint hatte, blieb der Block offen. <div class="content-body"> und </main> waren im generierten HTML invalide.

Fix: if in_sprint: html += "</div>" am Funktionsende ergänzt.

S1 parse_issues_md() — stray `<div>`, unkorrekte H1-Behandlung, letztes Issue offenBEHOBEN

Status: BEHOBEN v8.2.0

Seit: v8.0.0

Beschreibung: Drei Bugs in parse_issues_md(): (1) --- Trennzeichen schloss Karten-Divs auch wenn keine Karte offen war → stray </div>. (2) # Datei-Header-Zeilen wurden als Body-Text gerendert statt übersprungen. (3) Letztes Issue wurde nie geschlossen (kein --- am Dateiende).

Fix: in_card-Tracking, H1-Skip, explizites Schließen am Funktionsende.

S1 W3C-Validierungsfehler — `<section>` ohne direktes HeadingBEHOBEN

Status: BEHOBEN v8.2.0

Seit: v8.0.0

Beschreibung: Roadmap-, Issues- und Werft-Tab-Container verwendeten <section> ohne direktes <h2>/<h3> im Scope. W3C Nu Html Checker: "warning: Section lacks heading."

Fix: <section> in Tab-Panels durch <div> ersetzt.

S1 Sensible Build-Daten auf allen Seiten sichtbarBEHOBEN

Status: BEHOBEN v8.4.0

Seit: v8.3.0

Beschreibung: claudeData (KI-Stunden, Sessionen), changelogKpis, issuesKpis, engineData (Git-SHA, Unix-Timestamps, Repo-Größe) waren im HTML-Source jeder Episodenseite als window.HU_CONFIG sichtbar. Datenschutz-Problem: Arbeitszeitmuster, Fingerabdrücke, Metadaten öffentlich reproduzierbar.

Fix: make_hu_config(changelog_page=False) — sensible Felder nur auf changelog.html. Alle anderen Seiten: {buildMonth: "YYYY-MM"} statt vollem engineData.

S2 KI-Sektion Claude-spezifisch — andere LLMs nicht abbildbarBEHOBEN

Status: BEHOBEN v8.6.0

Seit: v8.3.0

Beschreibung: Config-Keys CLAUDE_* und Sektion "CLAUDE AI NUTZUNG" setzten Claude als einziges KI-Tool voraus. Verwendung von ChatGPT, Gemini etc. parallel nicht dokumentierbar.

Fix: CLAUDE_*KI_*, Sektion umbenannt in "CO-PILOT — KI-ASSISTENZ", KI_TOOLS als Freitext-Feld.

S1 Claude-Nutzungsdaten via CSV — Free-Tier hat keinen ExportBEHOBEN

Status: BEHOBEN v8.5.0

Seit: v8.3.0

Beschreibung: parse_claude_csv() erwartete CSV-Export aus der Anthropic Console. claude.ai Free/Pro hat keinen API-Zugang und damit keinen Token-Export. Build lief ohne claude_usage.csv fehlerhaft oder mit Dummy-Daten.

Fix: CSV-basierter Ansatz komplett entfernt. Manuelle Pflege via KI_SESSIONS, KI_HOURS, KI_HOURS_HUMAN in config.py. Schätzwerte klar als solche gekennzeichnet.

S1 OP3 API: showUuid vs. podcastGuid verwechseltBEHOBEN

Status: BEHOBEN v7.2.4

Seit: v7.2.0

Beschreibung: Download- und Query-Endpoints der OP3-API erwarten die interne showUuid, nicht die podcastGuid aus dem RSS-Feed. Alle Requests gaben HTTP 400 zurück.

Fix: show_uuid = show.get("showUuid") nach initialem /shows/{podcastGuid}-Fetch.

S1 OP3 API: falsche Endpunkt-Pfade (Plural/Singular, Datumsformat)BEHOBEN

Status: BEHOBEN v7.2.3

Seit: v7.2.0

Beschreibung: Drei Endpunkte falsch: downloads/shows/ (Plural) statt downloads/show/ (Singular), Datumsformat YYYY-MM-DD statt YYYY-MM, falsches Query-Endpoint-Schema.

Fix: Alle drei Endpunkte korrigiert.

S1 OP3 Datenformat — TSV statt JSON, dict statt rowsBEHOBEN

Status: BEHOBEN v7.2.5

Seit: v7.2.3

Beschreibung: /downloads/show/ liefert TSV, kein JSON. top-apps-for-show liefert appDownloads-Dict, kein rows-Array. JS erwartete .rows-Zugriff.

Fix: TSV-Endpoint aus Statistik-Nutzung entfernt, dict-Mapping korrigiert.

S1 OP3 ReferenceError: dlData nicht definiertBEHOBEN

Status: BEHOBEN v7.2.6

Seit: v7.2.5

Beschreibung: dlData noch aus alter fetch()-Version referenziert (if (dlData?.rows?.length)), nach Refactoring nicht mehr vorhanden. Warf ReferenceError im try-Block — catch zeigte irreführend "OP3 API nicht erreichbar".

Fix: Veralteten Block entfernt, Sparkline aus monthly.weeklyDownloads aufgebaut.

S2 OP3 Token in config.py und HTML-Source sichtbarBEHOBEN

Status: BEHOBEN v7.2.2

Seit: v7.2.0

Beschreibung: OP3_TOKEN war in config.py und wurde in window.HU_CONFIG ins HTML eingebettet — Token im Browser-Quelltext öffentlich sichtbar.

Fix: Token nur noch als os.environ["OP3_TOKEN"] zur Build-Zeit — nie in Git, nie im HTML.

S1 Transkript-Tab leer — IIFE-Scope-BugBEHOBEN

Status: BEHOBEN v7.6.4 / v7.5.1

Seit: v7.5.0

Beschreibung: Transkript im Player-Sheet wurde nicht geladen. initSheetTabs() lief bei DOMContentLoaded bevor Player.inject() #bp-sheet ins DOM schrieb. getElementById("bp-sheet") war null, früher Return, kein Click-Listener. Fehler lautlos, kein Console-Error.

Fix: Event-Delegation auf document statt Init-Guard auf spezifischem Element.

S1 Player-Bar auf Home und Archiv nie sichtbarBEHOBEN

Status: BEHOBEN v7.6.6

Seit: v7.0.0

Beschreibung: Player.inject() lief nur wenn audio.card-audio im DOM vorhanden — das gibt es nur auf Episodenseiten. Auf Home (index.html) und Archiv (archive.html) wurde #bp-bar nie erzeugt. Audio lief (unsichtbar), aber keinerlei Bedienelemente.

Fix: Guard entfernt, Player.inject() bedingungslos.

S1 Doppelter Play-Handler — Audio startete und stoppte sofortBEHOBEN

Status: BEHOBEN v7.6.7

Seit: v7.0.0

Beschreibung: Inline-Script in build_index() UND setupEpisodeCards() in app.js feuerten gleichzeitig auf denselben Play-Buttons. Klick startete Audio → zweiter Handler stoppte es sofort.

Fix: Inline-Script in build_index() vollständig entfernt. setupEpisodeCards() ist einzige Quelle für Card-Interaktionen.

S2 Like-Button auf Latest-Episode-Card nicht funktionalBEHOBEN

Status: BEHOBEN v7.1.0

Seit: v7.0.0

Beschreibung: setupEpisodeCards() nutzte querySelectorAll(".episode-card") — erfasste .latest-episode-card (Featured Card auf der Startseite) nicht. Like-Button auf der obersten Karte hatte keinen Handler.

Fix: querySelectorAll(".episode-card, .latest-episode-card").

S2 Nav auf Episodenseiten — inkonsistente Anker-LinksBEHOBEN

Status: BEHOBEN v7.4.2

Seit: v7.4.0

Beschreibung: template.html hatte noch 6 Nav-Links inklusive #Abonnieren und #NeusteFolge als Anker. Auf Nicht-Index-Seiten führten diese Links nirgendwo hin (Anker existieren nur auf index.html). Konfus für Nutzer und Screenreader.

Fix: Nav auf 4 echte Seiten (Start/Hangar/Logbuch/Radarbild) reduziert, konsistent auf allen Seitentypen.

S2 Player.inject() auf Logbuch/Radarbild — "keine Datei ausgewählt"-BugBEHOBEN

Status: BEHOBEN v7.4.2

Seit: v7.4.0

Beschreibung: Player.inject() lief auf Allen Seiten nach Guard-Entfernung (v7.6.6). Auf Logbuch und Radarbild ohne Audio-Elemente: leere Player-Bar mit "keine Datei ausgewählt" erschien kurz.

Fix: Guard nur für vollständigen Inject, nicht für Bar-Initialisierung.

S2 Footer auf statistiken.html fehlteBEHOBEN

Status: BEHOBEN v7.4.0

Seit: v7.3.0

Beschreibung: Die Statistiken-Seite wurde ohne Footer generiert — make_footer() nicht in build_stats() aufgerufen.

Fix: make_footer(version) in build_stats() ergänzt.

S3 W3C-Fehler — Pipe in Bunny-Fonts-URL nicht URL-encodedBEHOBEN

Status: BEHOBEN v7.3.3

Seit: v7.0.0

Beschreibung: | zwischen Fontnamen in der Bunny-CDN-URL war nicht als %7C encoded, & nicht als &amp; — W3C Nu Html Checker: Validierungsfehler auf allen generierten Seiten.

Fix: URL-Encoding in make_head() korrigiert.

S2 Inline `style="..."` in build.py und app.js — CSS-Separation verletztBEHOBEN

Status: BEHOBEN v7.3.1–v7.3.2

Seit: v7.0.0

Beschreibung: Ca. 15 Stellen in build.py und app.js nutzten style.cssText oder inline style=-Attribute statt CSS-Klassen. Verhinderte einheitliches Theming, schwer zu überschreiben.

Fix: Alle inline Styles auf dedizierte CSS-Klassen migriert.

S2 abflug.sh — kein `set -e`, Push bei fehlgeschlagenem Build möglichBEHOBEN

Status: BEHOBEN v7.5.0

Seit: Projektstart

Beschreibung: abflug.sh hatte kein set -e und kein set -o pipefail. Bei Python-Fehlern oder fehlgeschlagenem Build wurde trotzdem gepusht — defekte Builds auf Codeberg Pages deployed.

Fix: set -e + set -o pipefail am Script-Anfang.

S2 Kapitel-Highlighting — Off-by-one durch GleitkommaBEHOBEN

Status: BEHOBEN v7.6.9

Seit: v7.6.x

Beschreibung: Aktives Kapitel im Sheet (chapter-item.active) sprang auf falschen Eintrag oder reagierte verzögert. highlightChapter() verglich aud.currentTime mit parseFloat(dataset.start) — Gleitkomma-Rounding (180.0 vs. 179.98) führt zu Off-by-one bei kurzen Kapiteln.

Fix: Toleranz von 0.5s beim Vergleich.

S3 Service-Worker Cache nach Deploy manchmal veraltetBEHOBEN

Status: BEHOBEN v7.8.0

Seit: Projektstart

Beschreibung: Nutzer sahen nach Deploy gelegentlich ältere Versionen. skipWaiting() gesetzt, aber Browser warteten manchmal Tab-Reload für SW-Aktivierung.

Fix: clients.claim() in activate-Handler, SW-Update-Toast mit aria-live=polite.

S3 Console.log zeigte hardcodierte Version statt aktuelleBEHOBEN

Status: BEHOBEN v7.6.7

Seit: v7.0.0

Beschreibung: Startup-Log in app.js hatte Version hardcoded (v7.0.0) statt window.HU_CONFIG.version zu lesen.

Fix: Template-String liest HU_CONFIG?.version.

S2 `parseFloat()` kann `HH:MM:SS`-Timestamps nicht parsenBEHOBEN

Status: BEHOBEN v7.2.x

Seit: v7.2.0

Beschreibung: Chapter-JSON enthält Timestamps als "01:23:45" Strings. parseFloat("01:23:45") gibt 1 zurück (parst bis zum ersten Nicht-Ziffern-Zeichen). Kapitel sprangen zur falschen Zeit.

Fix: Dedizierte parseChTime() Funktion die HH:MM:SS, MM:SS und Sekunden-Zahl als Fallback korrekt auflöst.

S2 `contain: *` auf Cards — box-shadow abgeschnittenBEHOBEN

Status: BEHOBEN (Anti-Pattern dokumentiert)

Seit: v7.0.0

Beschreibung: Einsatz von contain: layout oder contain: strict auf .card-Elementen schnitt box-shadow ab (CSS-Spec: contain erstellt neuen Stacking Context). Cards sahen abgehackt aus.

Fix: contain komplett von Cards entfernt. In AGENTS.md als kritisches Anti-Pattern dokumentiert.

S2 `overflow: hidden` auf `.hero-section` — Gradient-Blend unterbrochenBEHOBEN

Status: BEHOBEN (Anti-Pattern dokumentiert)

Seit: v7.0.0

Beschreibung: overflow: hidden auf der Hero-Sektion unterbrach den Gradient-Blend-Effekt zum Seitenhintergrund. Harter optischer Abschnitt statt sanftem Übergang.

Fix: overflow: hidden auf .hero-section entfernt. Anti-Pattern in AGENTS.md.

S2 Scroll-Reveal `opacity: 0` auf erster Card — Content bei langsamem JS unsichtbarBEHOBEN

Status: BEHOBEN (Anti-Pattern dokumentiert)

Seit: v7.0.0

Beschreibung: .js .card { opacity: 0 } für Scroll-Reveal-Animationen traf auch die erste Card. Wenn JS langsam initialisierte oder blockiert war, blieb die erste Card dauerhaft unsichtbar.

Fix: .js .card:first-of-type { opacity: 1 !important; transform: none !important } als Ausnahme. Anti-Pattern in AGENTS.md.

S2 `card-link-overlay` mit `position: absolute; inset: 0` auf interaktiven CardsBEHOBEN

Status: BEHOBEN (Anti-Pattern dokumentiert)

Seit: v7.0.0

Beschreibung: Overlay-Links über die gesamte Card fingen alle Click-Events ab — Buttons, Like-Icons, Zap-Buttons innerhalb der Card reagierten nicht mehr.

Fix: Overlay-Pattern auf Cards mit interaktiven Kindern entfernt. Anti-Pattern in AGENTS.md.

S3 `localStorage` ohne try/catch — Crash im Private-ModeBEHOBEN

Status: BEHOBEN (Anti-Pattern dokumentiert)

Seit: v7.0.0

Beschreibung: Direkte localStorage-Zugriffe in einigen Stellen ohne try/catch. In bestimmten Browsern im Inkognito-Modus oder bei vollen Storage-Quotas wirft localStorage.setItem() eine Exception — unkontrollierter Crash.

Fix: Alle localStorage-Zugriffe in try/catch. Anti-Pattern in AGENTS.md.

S3 `<button>` ohne `type="button"` — unbeabsichtigtes Form-SubmitBEHOBEN

Status: BEHOBEN (Anti-Pattern dokumentiert)

Seit: v7.0.0

Beschreibung: Mehrere <button>-Elemente ohne explizites type="button". Standard ist type="submit" — in einem Form-Kontext würde ein Klick das Form submitten statt den Handler auszuführen.

Fix: Alle Buttons im dynamisch generierten HTML explizit mit type="button". Anti-Pattern in AGENTS.md.

S3 Kontrast-Mängel — Nav, Beschreibungen, Buttons unter WCAG AABEHOBEN

Status: BEHOBEN v7.1.0

Seit: v7.0.0

Beschreibung: Nav-Links mit --text-faint (22% Opacity), Episode-Beschreibungen mit 50% Opacity, Like- und Details-Buttons kaum sichtbar. Auf OLED-Displays faktisch unsichtbar.

Fix: Nav → --text-dim (50%), ep-desc--text mit 70% Opacity, Buttons angehoben.

S3 Bullets im Logbuch unsichtbar — CSS-ResetBEHOBEN

Status: BEHOBEN v7.1.0

Seit: v7.4.0

Beschreibung: Globaler CSS-Reset (list-style: none) entfernte alle Bullets. Changelog-Einträge mit <ul> hatten keine sichtbaren Listenpunkte.

Fix: .log-entry ul { list-style: disc; padding-left: 1.2em }, ::marker in Sunrise-Orange.

S3 `setInterval` ohne `clearInterval` beim Stoppen — Memory LeakBEHOBEN

Status: BEHOBEN (Anti-Pattern dokumentiert)

Seit: v7.2.0

Beschreibung: Sleep-Timer und V4V-Streaming nutzten setInterval ohne garantiertes clearInterval bei Stop/Pause/Destroy. Mehrfaches Starten/Stoppen akkumulierte laufende Intervals.

Fix: Alle setInterval-Stellen auf clearInterval-Pattern umgestellt. Anti-Pattern in AGENTS.md.

S2 `patchPlayerForSprint34` vor `Player.inject()` aufgerufen — Null-FehlerBEHOBEN

Status: BEHOBEN (Anti-Pattern dokumentiert)

Seit: v7.2.0

Beschreibung: Sprint-3/4-Patch-Funktion wurde an einer Stelle vor Player.inject() aufgerufen — DOM-Elemente (#bp-bar, #bp-sheet) existierten noch nicht, alle getElementById-Calls gaben null zurück, null-Referenz-Fehler folgten.

Fix: Aufruf-Reihenfolge korrigiert. Anti-Pattern in AGENTS.md.

S3 Hardcoded Podcast-Name und Pfade im JSBEHOBEN

Status: BEHOBEN v7.3.1

Seit: v7.0.0

Beschreibung: "Herr Urlaub..." und /statistiken.html waren direkt in app.js hardcoded. Bei Umbenennung des Podcasts oder der Statistik-Seite musste JS manuell angepasst werden.

Fix: window.HU_CONFIG.podcastName und window.HU_CONFIG.statsPage.

*Zuletzt aktualisiert: v8.13.0 — 12.03.2026*