{
  "id": "fennek",
  "name": "Fennek",
  "type": "fork",
  "maintainer": "danst0",
  "description": "An alternative open-source firmware for the LilyGO T-Deck Pro — music, audiobooks, eBooks and LoRa mesh chat via MeshCore. Features a homescreen launcher, touch and physical keyboard support, EPUB reader, audiobook player, and PCM5102A DAC audio output.\n",
  "repository": "https://github.com/danst0/fennek",
  "license": "GPL-3.0",
  "status": "active",
  "lifecycle": "active",
  "maturity": "beta",
  "distribution": "community",
  "lineage": {
    "kind": "fork",
    "upstreamFirmwareId": "meshcore-official",
    "upstreamRepository": "https://github.com/meshcore-dev/MeshCore"
  },
  "runtime": {
    "framework": "arduino",
    "language": "cpp"
  },
  "roles": [
    "companion",
    "standalone-ui"
  ],
  "features": [
    "Homescreen launcher",
    "Music playback (PCM5102A DAC)",
    "Audiobook player",
    "EPUB and TXT reader with page-index cache",
    "LoRa mesh chat via MeshCore",
    "Touch + physical keyboard support",
    "Per-book reading position"
  ],
  "capabilities": {
    "protocol": {
      "meshcoreCompatible": true
    },
    "transports": {
      "ble": true,
      "usbSerial": true,
      "nativeTcp": true,
      "wifiAp": true
    },
    "operations": {
      "ota": false,
      "webFlasher": false
    },
    "networking": {
      "repeater": false,
      "roomServer": false,
      "observer": false,
      "kissModem": false
    },
    "hardware": {
      "gps": true,
      "display": true,
      "sensors": false,
      "lowPowerRx": false
    }
  },
  "devices": [
    {
      "id": "lilygo-tdeck-pro",
      "status": "supported"
    }
  ],
  "popularity": {
    "githubStars": 2,
    "githubForks": 0,
    "githubWatchers": 0,
    "githubOpenIssues": 0,
    "githubContributors": 101,
    "releaseDownloads": 0,
    "latestReleaseDownloads": 0,
    "lastChecked": "2026-06-21"
  },
  "verification": {
    "sourceAvailable": true,
    "releasesAvailable": true,
    "ciBuilds": false,
    "lastChecked": "2026-06-21"
  },
  "source": {
    "path": "data/firmwares/fennek/firmware.yaml",
    "updatedAt": "2026-06-22T21:31:07+02:00"
  },
  "latest_version": "2.5.9",
  "released": "2026-06-21",
  "releases": [
    {
      "version": "v2.5.9",
      "name": "Fennek v2.5.9",
      "datetime": "2026-06-21T19:36:11Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v2.5.9",
      "prerelease": false,
      "notes": "Podcast-App deaktiviert (WLAN-Sync zu langsam).\n\n- Auto-WLAN-Sync vor Standby (podcast::flushBeforeStandby) auskommentiert — brachte bei jedem Standby das WLAN über den 4-MHz-SD-Bus hoch\n- Launcher-Kachel + appmgr-Registrierung + podcast::begin() aus → App vom Gerät verschwunden, Slot 13 (Seite 2) bleibt leer\n- Code/Konsolen-Befehle/NVS-Toggle/i18n bewusst behalten (reaktivierbar)",
      "notesHtml": "<p>Podcast-App deaktiviert (WLAN-Sync zu langsam).</p>\n<ul>\n<li>Auto-WLAN-Sync vor Standby (podcast::flushBeforeStandby) auskommentiert — brachte bei jedem Standby das WLAN über den 4-MHz-SD-Bus hoch</li>\n<li>Launcher-Kachel + appmgr-Registrierung + podcast::begin() aus → App vom Gerät verschwunden, Slot 13 (Seite 2) bleibt leer</li>\n<li>Code/Konsolen-Befehle/NVS-Toggle/i18n bewusst behalten (reaktivierbar)</li>\n</ul>\n"
    },
    {
      "version": "v2.5.7",
      "name": "v2.5.7",
      "datetime": "2026-06-21T19:05:52Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v2.5.7",
      "prerelease": false,
      "notes": "## Sudoku als fünftes Spiel\n\nNeues Spiel **Sudoku** in der Spiele-App (neben 2048, Minensucher, Schach, Tic-Tac-Toe).\n\n- Arduino-freier Generator + Solver (`sudoku_core.h`, host-getestet): randomisiertes Backtracking für die Vollösung, dann eindeutigkeitsprüfendes Ausgraben — erzeugt nur Rätsel mit genau einer Lösung. Rekursionsfrei (iterativer Level-Stack).\n- 9×9-Gitter à 26 px, Touch-Tap/WASD-Cursor, Zifferntasten 1–9 setzen, 0/Backspace/Mikrofon leeren, Konfliktzellen invertiert, „Gelöst\"-Overlay mit Zeit.\n- Schwierigkeit Leicht/Mittel/Schwer (40/32/26 Vorgaben).\n- Beststände im NVS (gelöste Rätsel + Bestzeit), inkl. INI-Export/-Import.\n\n**OTA:** `firmware.bin` angehängt.",
      "notesHtml": "<h2>Sudoku als fünftes Spiel</h2>\n<p>Neues Spiel <strong>Sudoku</strong> in der Spiele-App (neben 2048, Minensucher, Schach, Tic-Tac-Toe).</p>\n<ul>\n<li>Arduino-freier Generator + Solver (<code>sudoku_core.h</code>, host-getestet): randomisiertes Backtracking für die Vollösung, dann eindeutigkeitsprüfendes Ausgraben — erzeugt nur Rätsel mit genau einer Lösung. Rekursionsfrei (iterativer Level-Stack).</li>\n<li>9×9-Gitter à 26 px, Touch-Tap/WASD-Cursor, Zifferntasten 1–9 setzen, 0/Backspace/Mikrofon leeren, Konfliktzellen invertiert, „Gelöst\"-Overlay mit Zeit.</li>\n<li>Schwierigkeit Leicht/Mittel/Schwer (40/32/26 Vorgaben).</li>\n<li>Beststände im NVS (gelöste Rätsel + Bestzeit), inkl. INI-Export/-Import.</li>\n</ul>\n<p><strong>OTA:</strong> <code>firmware.bin</code> angehängt.</p>\n"
    },
    {
      "version": "v2.5.6",
      "name": "v2.5.6",
      "datetime": "2026-06-21T15:32:44Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v2.5.6",
      "prerelease": false,
      "notes": "fix(power): Dateien-Modus am Kabel nicht einschlafen lassen\n\n- battery::external() (DSG-Bit aus BatteryStatus 0x0A) erkennt Netzteil zuverlässig, auch bei vollem Akku (charging() fiel bei Strom ~0 auf false)\n- power::poll() nutzt external() statt charging() für die Dateien-Standby-Bremse",
      "notesHtml": "<p>fix(power): Dateien-Modus am Kabel nicht einschlafen lassen</p>\n<ul>\n<li>battery::external() (DSG-Bit aus BatteryStatus 0x0A) erkennt Netzteil zuverlässig, auch bei vollem Akku (charging() fiel bei Strom ~0 auf false)</li>\n<li>power::poll() nutzt external() statt charging() für die Dateien-Standby-Bremse</li>\n</ul>\n"
    },
    {
      "version": "v2.5.5",
      "name": "v2.5.5",
      "datetime": "2026-06-21T15:07:48Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v2.5.5",
      "prerelease": false,
      "notes": "feat(wecker): Einmalig-Option für Wecker\n\n- Neues Alarm-Feld `once` (NVS byte0 Bit4, abwärtskompatibel)\n- Einmalige Wecker feuern am nächsten hh:mm tageunabhängig und schalten sich danach selbst aus\n- Editor-Preset \"Einmalig\" im Feld Tage; Liste/Editor zeigen es an\n- Konsole: `alarm <i> <hh:mm> einmal` (once/1x)\n\nOTA: firmware.bin angehängt.",
      "notesHtml": "<p>feat(wecker): Einmalig-Option für Wecker</p>\n<ul>\n<li>Neues Alarm-Feld <code>once</code> (NVS byte0 Bit4, abwärtskompatibel)</li>\n<li>Einmalige Wecker feuern am nächsten hh:mm tageunabhängig und schalten sich danach selbst aus</li>\n<li>Editor-Preset \"Einmalig\" im Feld Tage; Liste/Editor zeigen es an</li>\n<li>Konsole: <code>alarm &lt;i&gt; &lt;hh:mm&gt; einmal</code> (once/1x)</li>\n</ul>\n<p>OTA: firmware.bin angehängt.</p>\n"
    },
    {
      "version": "v2.5.4",
      "name": "v2.5.4",
      "datetime": "2026-06-21T15:00:17Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v2.5.4",
      "prerelease": false,
      "notes": "OTA-Update-Fix + Ollama-Timeout.\n\n- **OTA `-104` behoben:** GitHub liefert für Release-Assets eine signierte Redirect-URL (`release-assets.githubusercontent.com`, ~900 Zeichen). Diese sprengte den 300-Byte-Puffer in `apply()` → abgeschnittene URL → `httpUpdate`-Fehler `-104` (wrong http code). Puffer auf 1400 Byte vergrößert.\n- **notes_ai (Ollama) Timeout:** `HTTPClient::setTimeout()` nimmt `uint16_t` (max 65535 ms); der Wert 120000 lief still auf 54464 ms über. Konstante auf 65000 ms korrigiert.\n\nErstes Release, mit dem OTA von einer früheren Version aus zuverlässig durchläuft.",
      "notesHtml": "<p>OTA-Update-Fix + Ollama-Timeout.</p>\n<ul>\n<li><strong>OTA <code>-104</code> behoben:</strong> GitHub liefert für Release-Assets eine signierte Redirect-URL (<code>release-assets.githubusercontent.com</code>, ~900 Zeichen). Diese sprengte den 300-Byte-Puffer in <code>apply()</code> → abgeschnittene URL → <code>httpUpdate</code>-Fehler <code>-104</code> (wrong http code). Puffer auf 1400 Byte vergrößert.</li>\n<li><strong>notes_ai (Ollama) Timeout:</strong> <code>HTTPClient::setTimeout()</code> nimmt <code>uint16_t</code> (max 65535 ms); der Wert 120000 lief still auf 54464 ms über. Konstante auf 65000 ms korrigiert.</li>\n</ul>\n<p>Erstes Release, mit dem OTA von einer früheren Version aus zuverlässig durchläuft.</p>\n"
    },
    {
      "version": "v2.5.3",
      "name": "v2.5.3",
      "datetime": "2026-06-21T14:48:58Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v2.5.3",
      "prerelease": false,
      "notes": "perf(dateien): Request-Zähler-Refresh 10x seltener gegen E-Ink-Flackern\n\n- Drosselung des Request-Zähler-Voll-Redraws im RUNNING-Zustand von 5 s auf 50 s\n- Zähler ist reine Info; seltenere Voll-Redraws sparen sichtbares E-Ink-Flackern",
      "notesHtml": "<p>perf(dateien): Request-Zähler-Refresh 10x seltener gegen E-Ink-Flackern</p>\n<ul>\n<li>Drosselung des Request-Zähler-Voll-Redraws im RUNNING-Zustand von 5 s auf 50 s</li>\n<li>Zähler ist reine Info; seltenere Voll-Redraws sparen sichtbares E-Ink-Flackern</li>\n</ul>\n"
    },
    {
      "version": "v2.5.2",
      "name": "Fennek v2.5.2",
      "datetime": "2026-06-21T14:29:15Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v2.5.2",
      "prerelease": false,
      "notes": "## Highlights\n\n- **On-Device-OTA-Knopf** (Settings → Update → „Aktualisieren\"): prüft GitHub-Releases und flasht direkt am Gerät mit Vollbild-Fortschritt (Phase + Balken + %). Kein Browser/Konsole mehr nötig.\n- **Notizen:** Audionotiz-Player (Play/Pause, ±10 s, Region-Fortschritt) + hoch-scrollbarer Editor.\n- **Karteikarten:** Übersicht mit Leitner-Fächer-Verteilung (fällig/bearbeitet).\n- **Kopfrechnen:** adaptive Schwierigkeit, je-Modus persistierte Stufe.\n- **notes_ai:** behutsamerer Korrektur-Prompt (nur Rechtschreibung/Umlaute, kein Umdichten).\n- **Tools:** `maps_tiles.py` mit wählbaren Kachelquellen (`--preset standard|cyclosm|topo`).\n\n## OTA-Update\n\nGeräte ab v2.5.1 können direkt am Gerät auf diese Version aktualisieren (Settings → Update → „Aktualisieren\") oder via Web-UI (`fennek.local`) / Konsole (`ota update`).\n\n**Flashen (USB):** `firmware.bin` in den inaktiven OTA-Slot bzw. via `pio run -e fennek -t upload`.",
      "notesHtml": "<h2>Highlights</h2>\n<ul>\n<li><strong>On-Device-OTA-Knopf</strong> (Settings → Update → „Aktualisieren\"): prüft GitHub-Releases und flasht direkt am Gerät mit Vollbild-Fortschritt (Phase + Balken + %). Kein Browser/Konsole mehr nötig.</li>\n<li><strong>Notizen:</strong> Audionotiz-Player (Play/Pause, ±10 s, Region-Fortschritt) + hoch-scrollbarer Editor.</li>\n<li><strong>Karteikarten:</strong> Übersicht mit Leitner-Fächer-Verteilung (fällig/bearbeitet).</li>\n<li><strong>Kopfrechnen:</strong> adaptive Schwierigkeit, je-Modus persistierte Stufe.</li>\n<li><strong>notes_ai:</strong> behutsamerer Korrektur-Prompt (nur Rechtschreibung/Umlaute, kein Umdichten).</li>\n<li><strong>Tools:</strong> <code>maps_tiles.py</code> mit wählbaren Kachelquellen (<code>--preset standard|cyclosm|topo</code>).</li>\n</ul>\n<h2>OTA-Update</h2>\n<p>Geräte ab v2.5.1 können direkt am Gerät auf diese Version aktualisieren (Settings → Update → „Aktualisieren\") oder via Web-UI (<code>fennek.local</code>) / Konsole (<code>ota update</code>).</p>\n<p><strong>Flashen (USB):</strong> <code>firmware.bin</code> in den inaktiven OTA-Slot bzw. via <code>pio run -e fennek -t upload</code>.</p>\n"
    },
    {
      "version": "v2.5.0",
      "name": "v2.5.0",
      "datetime": "2026-06-21T10:11:51Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v2.5.0",
      "prerelease": false,
      "notes": "**Full Changelog**: https://github.com/danst0/fennek/compare/v2.4.9...v2.5.0",
      "notesHtml": "<p><strong>Full Changelog</strong>: <a href=\"https://github.com/danst0/fennek/compare/v2.4.9...v2.5.0\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/danst0/fennek/compare/v2.4.9...v2.5.0</a></p>\n"
    },
    {
      "version": "v2.4.8",
      "name": "Fennek v2.4.8",
      "datetime": "2026-06-20T19:59:52Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v2.4.8",
      "prerelease": false,
      "notes": "## Änderungen\n\n- **OTA:** Semver-Vergleich für Update-Erkennung (`tag_name != FENNEK_VERSION` → korrekte Versionslogik)\n- **Akku:** Batterieanzeige-Fix in der Statuszeile\n\n## Flashen\n\n```\npio run -e fennek -t upload\n```\n\noder OTA via `fennek.local` → Firmware-Update.",
      "notesHtml": "<h2>Änderungen</h2>\n<ul>\n<li><strong>OTA:</strong> Semver-Vergleich für Update-Erkennung (<code>tag_name != FENNEK_VERSION</code> → korrekte Versionslogik)</li>\n<li><strong>Akku:</strong> Batterieanzeige-Fix in der Statuszeile</li>\n</ul>\n<h2>Flashen</h2>\n<pre><code>pio run -e fennek -t upload\n</code></pre>\n<p>oder OTA via <code>fennek.local</code> → Firmware-Update.</p>\n"
    },
    {
      "version": "v2.3.1",
      "name": "Fennek v2.3.1",
      "datetime": "2026-06-19T14:27:33Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v2.3.1",
      "prerelease": false,
      "notes": "## Notizen / Audio\n- **Sprachnotiz-Liste** zeigt jetzt **Datum + HH:MM** (statt HH:MM:SS bzw. generisch „Sprachnotiz\").\n- **Aufnahme-Gain ×6** mit Sättigung: das PDM-Mikro war ~−20 dBFS leise → Wiedergabe kaum hörbar. Neue Aufnahmen sitzen bei ~−7 dBFS (kein Clipping).\n- **Sauberer I2S0-Handover** nach der Aufnahme: kein doppeltes `i2s_driver_uninstall` mehr (`endMic`/`MicResume` macht uninstall+install in einem Schritt).\n\n## Karten / GPS\n- **No-Fix-Fallback** zentriert jetzt auf **Düsseldorf** (51.2277/6.7735); `DEFAULT_ZOOM` 15 → 13 (NRW-weit bekachelt).\n- **GPS-Zeit-Diagnose**: Einmal-Log `[MAPS] GPS-Zeit empfangen …` pro Karten-Session.\n\nEnthält außerdem v2.3.0 (GPS-/WAV-Diagnose-Logs, Karten-Fallback, Notizen-Zeitlabel).",
      "notesHtml": "<h2>Notizen / Audio</h2>\n<ul>\n<li><strong>Sprachnotiz-Liste</strong> zeigt jetzt <strong>Datum + HH:MM</strong> (statt HH:MM:SS bzw. generisch „Sprachnotiz\").</li>\n<li><strong>Aufnahme-Gain ×6</strong> mit Sättigung: das PDM-Mikro war ~−20 dBFS leise → Wiedergabe kaum hörbar. Neue Aufnahmen sitzen bei ~−7 dBFS (kein Clipping).</li>\n<li><strong>Sauberer I2S0-Handover</strong> nach der Aufnahme: kein doppeltes <code>i2s_driver_uninstall</code> mehr (<code>endMic</code>/<code>MicResume</code> macht uninstall+install in einem Schritt).</li>\n</ul>\n<h2>Karten / GPS</h2>\n<ul>\n<li><strong>No-Fix-Fallback</strong> zentriert jetzt auf <strong>Düsseldorf</strong> (51.2277/6.7735); <code>DEFAULT_ZOOM</code> 15 → 13 (NRW-weit bekachelt).</li>\n<li><strong>GPS-Zeit-Diagnose</strong>: Einmal-Log <code>[MAPS] GPS-Zeit empfangen …</code> pro Karten-Session.</li>\n</ul>\n<p>Enthält außerdem v2.3.0 (GPS-/WAV-Diagnose-Logs, Karten-Fallback, Notizen-Zeitlabel).</p>\n"
    },
    {
      "version": "v2.0.4",
      "name": "v2.0.4",
      "datetime": "2026-06-17T17:30:26Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v2.0.4",
      "prerelease": false,
      "notes": "## Changes\n\n- **Regenerated README/website screenshots.** The home-screen mirror in the host renderer (`tools/screenshot.cpp`) was stuck on an old layout (6 tiles, 2×3); it now matches the real `launcher.cpp` — a 2×5 grid with all 10 slots (Music/Listen/Read/Mesh/Settings/Games/Files/Notes/Alarm). The Settings screenshot now shows the current version.\n- Images updated in `docs/screenshots/{de,en,sv}/` and on [fennek.dumke.me](https://fennek.dumke.me).\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)",
      "notesHtml": "<h2>Changes</h2>\n<ul>\n<li><strong>Regenerated README/website screenshots.</strong> The home-screen mirror in the host renderer (<code>tools/screenshot.cpp</code>) was stuck on an old layout (6 tiles, 2×3); it now matches the real <code>launcher.cpp</code> — a 2×5 grid with all 10 slots (Music/Listen/Read/Mesh/Settings/Games/Files/Notes/Alarm). The Settings screenshot now shows the current version.</li>\n<li>Images updated in <code>docs/screenshots/{de,en,sv}/</code> and on <a href=\"https://fennek.dumke.me/\" target=\"_blank\" rel=\"noopener noreferrer\">fennek.dumke.me</a>.</li>\n</ul>\n<p>🤖 Generated with <a href=\"https://claude.com/claude-code\" target=\"_blank\" rel=\"noopener noreferrer\">Claude Code</a></p>\n"
    },
    {
      "version": "v1.9.4",
      "name": "v1.9.4",
      "datetime": "2026-06-17T06:53:30Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v1.9.4",
      "prerelease": false,
      "notes": "## Änderungen\n\n- **Web-Dateimanager**: Löschen ohne Rückfrage. Nur Ordner mit Unterordnern fragen kurz nach und werden dann rekursiv gelöscht (`rmRecursive`); Dateien und flache Ordner verschwinden sofort.\n- **Notizen-App + Konsole**: weitere Verbesserungen.\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)",
      "notesHtml": "<h2>Änderungen</h2>\n<ul>\n<li><strong>Web-Dateimanager</strong>: Löschen ohne Rückfrage. Nur Ordner mit Unterordnern fragen kurz nach und werden dann rekursiv gelöscht (<code>rmRecursive</code>); Dateien und flache Ordner verschwinden sofort.</li>\n<li><strong>Notizen-App + Konsole</strong>: weitere Verbesserungen.</li>\n</ul>\n<p>🤖 Generated with <a href=\"https://claude.com/claude-code\" target=\"_blank\" rel=\"noopener noreferrer\">Claude Code</a></p>\n"
    },
    {
      "version": "v1.9.2",
      "name": "Fennek v1.9.2",
      "datetime": "2026-06-15T15:51:06Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v1.9.2",
      "prerelease": false,
      "notes": "### Added\n- **`BATTLOG` — opt-in battery & activity logger** (`services/battlog`). When the `BATTLOG` build flag is set, the firmware writes one record per event (app switch, audio track, mesh radio on, Wi-Fi on/off, standby/wake) **and** a battery probe every 60 s to `/.fennek/battery.log` (timestamp + %/mV/charging? + Wi-Fi/mesh status). It's a throttled PSRAM ring flushed to SD before standby, and downloadable via the Web file manager — useful for diagnosing real-world battery drain. Deliberately zero-cost when the flag is off (all `BATTLOG_*` calls compile to nothing).\n\n🌐 Project page & screenshots: **https://fennek.dumke.me**\n\n**Full changelog:** [`CHANGELOG.md`](https://github.com/danst0/fennek/blob/main/CHANGELOG.md)",
      "notesHtml": "<h3>Added</h3>\n<ul>\n<li><strong><code>BATTLOG</code> — opt-in battery &amp; activity logger</strong> (<code>services/battlog</code>). When the <code>BATTLOG</code> build flag is set, the firmware writes one record per event (app switch, audio track, mesh radio on, Wi-Fi on/off, standby/wake) <strong>and</strong> a battery probe every 60 s to <code>/.fennek/battery.log</code> (timestamp + %/mV/charging? + Wi-Fi/mesh status). It's a throttled PSRAM ring flushed to SD before standby, and downloadable via the Web file manager — useful for diagnosing real-world battery drain. Deliberately zero-cost when the flag is off (all <code>BATTLOG_*</code> calls compile to nothing).</li>\n</ul>\n<p>🌐 Project page &amp; screenshots: <strong><a href=\"https://fennek.dumke.me/\" target=\"_blank\" rel=\"noopener noreferrer\">https://fennek.dumke.me</a></strong></p>\n<p><strong>Full changelog:</strong> <a href=\"https://github.com/danst0/fennek/blob/main/CHANGELOG.md\" target=\"_blank\" rel=\"noopener noreferrer\"><code>CHANGELOG.md</code></a></p>\n"
    },
    {
      "version": "v1.9.1",
      "name": "Fennek v1.9.1",
      "datetime": "2026-06-15T15:51:06Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v1.9.1",
      "prerelease": false,
      "notes": "### Fixed\n- **Notes never silently lost on a failed SD write.** The save path now clears its dirty flag only on a *complete* success; if an SD write or remove fails, the note stays dirty and is retried on the next autosave instead of being overwritten on the next editor open.\n- **Just-played track now reaches the scrobble queue before standby.** `power::poll()` stops audio (and lets the audio task drain the stop command) *before* the pre-standby Navidrome flush, so the current track is enqueued via `noteTrackEnded()` rather than landing in the PSRAM ring that deep sleep would otherwise discard.\n\n**Full changelog:** [`CHANGELOG.md`](https://github.com/danst0/fennek/blob/main/CHANGELOG.md)",
      "notesHtml": "<h3>Fixed</h3>\n<ul>\n<li><strong>Notes never silently lost on a failed SD write.</strong> The save path now clears its dirty flag only on a <em>complete</em> success; if an SD write or remove fails, the note stays dirty and is retried on the next autosave instead of being overwritten on the next editor open.</li>\n<li><strong>Just-played track now reaches the scrobble queue before standby.</strong> <code>power::poll()</code> stops audio (and lets the audio task drain the stop command) <em>before</em> the pre-standby Navidrome flush, so the current track is enqueued via <code>noteTrackEnded()</code> rather than landing in the PSRAM ring that deep sleep would otherwise discard.</li>\n</ul>\n<p><strong>Full changelog:</strong> <a href=\"https://github.com/danst0/fennek/blob/main/CHANGELOG.md\" target=\"_blank\" rel=\"noopener noreferrer\"><code>CHANGELOG.md</code></a></p>\n"
    },
    {
      "version": "v1.9.0",
      "name": "Fennek v1.9.0",
      "datetime": "2026-06-15T15:51:05Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v1.9.0",
      "prerelease": false,
      "notes": "### Added\n- **Mesh: `contacts reset` console command** + `mesh_client::resetContacts()`. Clears all contacts and their persistent mirrors (SPIFFS `/contacts`, SD `/meshcore/contacts.bin`); the contact table then rebuilds itself from incoming adverts. Handy when the 64-slot table fills up with stale nodes or you want a clean slate.\n\n**Full changelog:** [`CHANGELOG.md`](https://github.com/danst0/fennek/blob/main/CHANGELOG.md)",
      "notesHtml": "<h3>Added</h3>\n<ul>\n<li><strong>Mesh: <code>contacts reset</code> console command</strong> + <code>mesh_client::resetContacts()</code>. Clears all contacts and their persistent mirrors (SPIFFS <code>/contacts</code>, SD <code>/meshcore/contacts.bin</code>); the contact table then rebuilds itself from incoming adverts. Handy when the 64-slot table fills up with stale nodes or you want a clean slate.</li>\n</ul>\n<p><strong>Full changelog:</strong> <a href=\"https://github.com/danst0/fennek/blob/main/CHANGELOG.md\" target=\"_blank\" rel=\"noopener noreferrer\"><code>CHANGELOG.md</code></a></p>\n"
    },
    {
      "version": "v1.8.1",
      "name": "Fennek v1.8.1",
      "datetime": "2026-06-15T15:51:04Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v1.8.1",
      "prerelease": false,
      "notes": "### Changed\n- **Unified back/home navigation across all apps.** Home is now a visible affordance: the status bar shows a house glyph (⌂) outside the launcher and the whole bar taps to Home. Lower corner buttons are consistently labelled **\"Back\"** (exactly one level) — audiobook/reader/notes lists and Settings now say \"Back\" instead of \"Home\", and the Settings back button steps edit → category → root → launcher. Mesh sub-screens dropped their redundant second \"Home\" button, the Files app gained a visible \"Back\" button, and the reader footer tap-zone is labelled \"◄ List\". Games keep no on-screen back button (exit via the ⌂ glyph; keyboard `Backspace`/`Q` still works). Keyboard conventions (`Q` = Home, `Backspace` = one level back) unchanged.\n\n**Full changelog:** [`CHANGELOG.md`](https://github.com/danst0/fennek/blob/main/CHANGELOG.md)",
      "notesHtml": "<h3>Changed</h3>\n<ul>\n<li><strong>Unified back/home navigation across all apps.</strong> Home is now a visible affordance: the status bar shows a house glyph (⌂) outside the launcher and the whole bar taps to Home. Lower corner buttons are consistently labelled <strong>\"Back\"</strong> (exactly one level) — audiobook/reader/notes lists and Settings now say \"Back\" instead of \"Home\", and the Settings back button steps edit → category → root → launcher. Mesh sub-screens dropped their redundant second \"Home\" button, the Files app gained a visible \"Back\" button, and the reader footer tap-zone is labelled \"◄ List\". Games keep no on-screen back button (exit via the ⌂ glyph; keyboard <code>Backspace</code>/<code>Q</code> still works). Keyboard conventions (<code>Q</code> = Home, <code>Backspace</code> = one level back) unchanged.</li>\n</ul>\n<p><strong>Full changelog:</strong> <a href=\"https://github.com/danst0/fennek/blob/main/CHANGELOG.md\" target=\"_blank\" rel=\"noopener noreferrer\"><code>CHANGELOG.md</code></a></p>\n"
    },
    {
      "version": "v1.8.0",
      "name": "Fennek v1.8.0",
      "datetime": "2026-06-13T19:18:10Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v1.8.0",
      "prerelease": false,
      "notes": "## Added\n- **Notes app** (launcher tile 7): a daily-notes model — exactly one note per day, named after the local date (`/notes/YYYY-MM-DD.md`). **\"+ Today\"** opens or appends to today's note; the list shows every day newest-first with a first-line preview, and notes can be deleted with a confirmation. The editor is append-style (type / Backspace / Enter) with its own word wrap and a compact header.\n\n  Persistence is chunked under `spiLock` (never inside `draw()`), with a 30 s autosave and a save on leave; empty notes are removed. Without an SD card it shows \"No SD card\" + \"Search again\" like the other apps. The date comes from the local system clock (`timesync`). New console command `notes` runs an SD round-trip / scan self-check (in the style of `books`).\n\nLocalized in DE/IT/SV/EN/ES. Verified on device (clean boot, SD round-trip with UTF-8 preserved, scan/sort).\n\n**Full changelog:** [CHANGELOG.md](https://github.com/danst0/fennek/blob/main/CHANGELOG.md)",
      "notesHtml": "<h2>Added</h2>\n<ul>\n<li><p><strong>Notes app</strong> (launcher tile 7): a daily-notes model — exactly one note per day, named after the local date (<code>/notes/YYYY-MM-DD.md</code>). <strong>\"+ Today\"</strong> opens or appends to today's note; the list shows every day newest-first with a first-line preview, and notes can be deleted with a confirmation. The editor is append-style (type / Backspace / Enter) with its own word wrap and a compact header.</p>\n<p>Persistence is chunked under <code>spiLock</code> (never inside <code>draw()</code>), with a 30 s autosave and a save on leave; empty notes are removed. Without an SD card it shows \"No SD card\" + \"Search again\" like the other apps. The date comes from the local system clock (<code>timesync</code>). New console command <code>notes</code> runs an SD round-trip / scan self-check (in the style of <code>books</code>).</p>\n</li>\n</ul>\n<p>Localized in DE/IT/SV/EN/ES. Verified on device (clean boot, SD round-trip with UTF-8 preserved, scan/sort).</p>\n<p><strong>Full changelog:</strong> <a href=\"https://github.com/danst0/fennek/blob/main/CHANGELOG.md\" target=\"_blank\" rel=\"noopener noreferrer\">CHANGELOG.md</a></p>\n"
    },
    {
      "version": "v1.7.1",
      "name": "Fennek v1.7.1",
      "datetime": "2026-06-13T04:08:05Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v1.7.1",
      "prerelease": false,
      "notes": "## ✨ Added\n- **Settings backup as INI** (`services/settingsfile`): export/import all NVS settings to/from an SD file. SPI-free serialization on a RAM buffer, SD I/O isolated under `spiLock`. Per-book bookmarks/reading positions are excluded (CRC32-hashed keys). New console command + `main`/`power` wiring.\n\n## 📝 Docs\n- Added `CHANGELOG.md` and GitHub releases for v1.3.0–v1.7.0.\n- README now ships in **English (default)**, German (`README.de.md`) and Swedish (`README.sv.md`) with a language switcher and release/changelog badges.\n\n📋 Full history: [CHANGELOG.md](https://github.com/danst0/fennek/blob/main/CHANGELOG.md)",
      "notesHtml": "<h2>✨ Added</h2>\n<ul>\n<li><strong>Settings backup as INI</strong> (<code>services/settingsfile</code>): export/import all NVS settings to/from an SD file. SPI-free serialization on a RAM buffer, SD I/O isolated under <code>spiLock</code>. Per-book bookmarks/reading positions are excluded (CRC32-hashed keys). New console command + <code>main</code>/<code>power</code> wiring.</li>\n</ul>\n<h2>📝 Docs</h2>\n<ul>\n<li>Added <code>CHANGELOG.md</code> and GitHub releases for v1.3.0–v1.7.0.</li>\n<li>README now ships in <strong>English (default)</strong>, German (<code>README.de.md</code>) and Swedish (<code>README.sv.md</code>) with a language switcher and release/changelog badges.</li>\n</ul>\n<p>📋 Full history: <a href=\"https://github.com/danst0/fennek/blob/main/CHANGELOG.md\" target=\"_blank\" rel=\"noopener noreferrer\">CHANGELOG.md</a></p>\n"
    },
    {
      "version": "v1.7.0",
      "name": "Fennek v1.7.0",
      "datetime": "2026-06-13T04:03:33Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v1.7.0",
      "prerelease": false,
      "notes": "## ✨ Added\n- **\"+ New Channel\" UI** for joining hashtag channels — a \"► New Channel\" row at the top of the chat list opens a join screen (keyboard types the `#name`, PSK derived from it, persisted to `channels.txt` on SD; jumps straight into the new channel after joining).\n\n## 🔄 Changed\n- **Robust time synchronization without a hardware RTC.** Canonical clock is now ESP32 system time (`SystemRTCClock`), which survives deep sleep. New `services/timesync` coordinator: opportunistic NTP (when Wi-Fi is up) + a top-up before auto-standby, with Mesh adverts as a passive source. Quality model (`estErr = learned drift × time since sync`) with exponential back-off, `s_clockConfident` bootstrap for the first advert after boot, POSIX time zone (default `Europe/Berlin` incl. DST), settings rows \"Time zone\"/\"Time\", and console `time` / `time set` / `time sync` / `tz`.\n\n📋 Full history: [CHANGELOG.md](https://github.com/danst0/fennek/blob/main/CHANGELOG.md)",
      "notesHtml": "<h2>✨ Added</h2>\n<ul>\n<li><strong>\"+ New Channel\" UI</strong> for joining hashtag channels — a \"► New Channel\" row at the top of the chat list opens a join screen (keyboard types the <code>#name</code>, PSK derived from it, persisted to <code>channels.txt</code> on SD; jumps straight into the new channel after joining).</li>\n</ul>\n<h2>🔄 Changed</h2>\n<ul>\n<li><strong>Robust time synchronization without a hardware RTC.</strong> Canonical clock is now ESP32 system time (<code>SystemRTCClock</code>), which survives deep sleep. New <code>services/timesync</code> coordinator: opportunistic NTP (when Wi-Fi is up) + a top-up before auto-standby, with Mesh adverts as a passive source. Quality model (<code>estErr = learned drift × time since sync</code>) with exponential back-off, <code>s_clockConfident</code> bootstrap for the first advert after boot, POSIX time zone (default <code>Europe/Berlin</code> incl. DST), settings rows \"Time zone\"/\"Time\", and console <code>time</code> / <code>time set</code> / <code>time sync</code> / <code>tz</code>.</li>\n</ul>\n<p>📋 Full history: <a href=\"https://github.com/danst0/fennek/blob/main/CHANGELOG.md\" target=\"_blank\" rel=\"noopener noreferrer\">CHANGELOG.md</a></p>\n"
    },
    {
      "version": "v1.6.3",
      "name": "v1.6.3",
      "datetime": "2026-06-13T04:03:32Z",
      "url": "https://github.com/danst0/fennek/releases/tag/v1.6.3",
      "prerelease": false,
      "notes": "## 📡 Mesh SD persistence & clock hardening\n- Contact mirror (`/meshcore/contacts.bin`, SD takes load priority over SPIFFS, deferred write from `loop()`) and joined hashtag channels (`/meshcore/channels.txt`, re-joined on boot).\n- RTC sync guard against future-clock outliers (bootstrap + forward jumps < 1 h, backward only on a 3× advert consensus).\n- Contact overflow fix: overwrite the oldest non-favorite when the table (64) is full instead of dropping new adverts.\n- Console hashtag commands `join` / `chan`, plus a new `i2cscan` command. Verified on device: the T-Deck Pro V1.1 has **no hardware RTC** (0x51 empty).",
      "notesHtml": "<h2>📡 Mesh SD persistence &amp; clock hardening</h2>\n<ul>\n<li>Contact mirror (<code>/meshcore/contacts.bin</code>, SD takes load priority over SPIFFS, deferred write from <code>loop()</code>) and joined hashtag channels (<code>/meshcore/channels.txt</code>, re-joined on boot).</li>\n<li>RTC sync guard against future-clock outliers (bootstrap + forward jumps &lt; 1 h, backward only on a 3× advert consensus).</li>\n<li>Contact overflow fix: overwrite the oldest non-favorite when the table (64) is full instead of dropping new adverts.</li>\n<li>Console hashtag commands <code>join</code> / <code>chan</code>, plus a new <code>i2cscan</code> command. Verified on device: the T-Deck Pro V1.1 has <strong>no hardware RTC</strong> (0x51 empty).</li>\n</ul>\n"
    }
  ],
  "changelogSource": "github",
  "changelogUpdatedAt": "2026-06-22T12:12:05.972Z"
}
