Skip to content

Releases: Abrechen2/TravStats

TravStats v1.1.0

18 Apr 15:25

Choose a tag to compare

Added

  • Birthday Flight achievement — New BIRTHDAY_FLIGHT achievement counts flown flights that depart on the user's birthday (month + day, year irrelevant). Profile settings now include an optional birthdate field used by the check.
  • Kurios calendar easter eggs — Six hidden achievements tied to aviation and calendar observances: ICAO Day (7 Dec), Wright Day (19 Aug), May the Fourth (4 May), Pi Day (14 Mar), Pi Precision (3 141 km ± 5 % on Pi Day) and Halloween (31 Oct).
  • Branded GlobeLoader — Monochrome spinning-globe loader replaces the generic spinner at every large-area loading surface (maps, stats, flight pages, admin). Paints in < 16 ms so users see feedback before deck.gl / three.js finish booting, holds for ≥ 2 s to avoid flashing, and keeps spinning regardless of prefers-reduced-motion. Reads live CSS tokens so it reacts to dark-mode toggles without remount.
  • New logo system (v1.0) — Luggage-tag mark with TS monogram and a cross-dot TRAV✛STATS wordmark ship as LogoMark, LogoMarkFilled, LogoWordmark and LogoLockup components. Visible in the nav header (desktop + mobile), all four auth screens and the favicon (SVG with dark background + PNG fallback). The README and Unraid forum / release docs point at docs/images/logo.svg.
  • App version vs build version split — Runtime now exposes the deployed app version (from backend/VERSION) and the Docker image build version as distinct fields, so About can show 1.1.0 (built from 1.1.0-rc.3) during the RC cycle.

Changed

  • Achievements modules splitbackend/src/utils/achievements.ts (1 073 lines) reorganised into three files (orchestrator + stats + checks); backend/src/data/achievements.ts (1 306 lines) split into two seed parts plus a thin composition layer. Public API unchanged. Every file now respects the 800-line cap.
  • Achievement stats are immutablecheckAndUpdateAchievements builds a fresh augmentedStats via spread rather than mutating the object returned from calculateUserStats; Sets are cloned, never mutated in place.

Fixed

  • GlobeLoader no longer freezes for reduced-motion users — The loader is feedback, not decoration; it now always animates.
  • Favicon visible on light browser chrome — The new SVG favicon shipped transparent, which made the amber strokes disappear on Safari / iOS tab bars. A dark background is now baked into the SVG.
  • LogoMarkFilled survives theme switches — Inverse colour used to be hardcoded #0b0d10; now reads var(--bg-base) so the mark stays legible in light mode.
  • Screen-reader announcements for the header logo and mobile drawer — Home link now announces as "TravStats — Home" once, not the visible text three times. Mobile drawer Donate / Star links carry the aria-label attributes their desktop twins already had.
  • GlobeLoader canvas no longer churns on parent rerenders — Derived buffer dimension is memoised so the effect dep array is stable.

Database

  • users.birthdate — New nullable TIMESTAMP column backing the Birthday Flight achievement. Additive only; no data transform; safe to deploy with zero downtime.

Tests

  • Brand/Logo component suite — 9 new Vitest tests cover the four logo components (a11y attributes, theme-token defaults, layout modes). Total frontend coverage is now 257 tests across 54 files.

TravStats v1.1.0-rc.1

18 Apr 15:05

Choose a tag to compare

TravStats v1.1.0-rc.1 Pre-release
Pre-release

Added

  • Birthday Flight achievement — New BIRTHDAY_FLIGHT achievement counts flown flights that depart on the user's birthday (month + day, year irrelevant). Profile settings now include an optional birthdate field used by the check.
  • Kurios calendar easter eggs — Six hidden achievements tied to aviation and calendar observances: ICAO Day (7 Dec), Wright Day (19 Aug), May the Fourth (4 May), Pi Day (14 Mar), Pi Precision (3 141 km ± 5 % on Pi Day) and Halloween (31 Oct).
  • Branded GlobeLoader — Monochrome spinning-globe loader replaces the generic spinner at every large-area loading surface (maps, stats, flight pages, admin). Paints in < 16 ms so users see feedback before deck.gl / three.js finish booting, holds for ≥ 2 s to avoid flashing, and keeps spinning regardless of prefers-reduced-motion. Reads live CSS tokens so it reacts to dark-mode toggles without remount.
  • New logo system (v1.0) — Luggage-tag mark with TS monogram and a cross-dot TRAV✛STATS wordmark ship as LogoMark, LogoMarkFilled, LogoWordmark and LogoLockup components. Visible in the nav header (desktop + mobile), all four auth screens and the favicon (SVG with dark background + PNG fallback). The README and Unraid forum / release docs point at docs/images/logo.svg.
  • App version vs build version split — Runtime now exposes the deployed app version (from backend/VERSION) and the Docker image build version as distinct fields, so About can show 1.1.0 (built from 1.1.0-rc.3) during the RC cycle.

Changed

  • Achievements modules splitbackend/src/utils/achievements.ts (1 073 lines) reorganised into three files (orchestrator + stats + checks); backend/src/data/achievements.ts (1 306 lines) split into two seed parts plus a thin composition layer. Public API unchanged. Every file now respects the 800-line cap.
  • Achievement stats are immutablecheckAndUpdateAchievements builds a fresh augmentedStats via spread rather than mutating the object returned from calculateUserStats; Sets are cloned, never mutated in place.

Fixed

  • GlobeLoader no longer freezes for reduced-motion users — The loader is feedback, not decoration; it now always animates.
  • Favicon visible on light browser chrome — The new SVG favicon shipped transparent, which made the amber strokes disappear on Safari / iOS tab bars. A dark background is now baked into the SVG.
  • LogoMarkFilled survives theme switches — Inverse colour used to be hardcoded #0b0d10; now reads var(--bg-base) so the mark stays legible in light mode.
  • Screen-reader announcements for the header logo and mobile drawer — Home link now announces as "TravStats — Home" once, not the visible text three times. Mobile drawer Donate / Star links carry the aria-label attributes their desktop twins already had.
  • GlobeLoader canvas no longer churns on parent rerenders — Derived buffer dimension is memoised so the effect dep array is stable.

Database

  • users.birthdate — New nullable TIMESTAMP column backing the Birthday Flight achievement. Additive only; no data transform; safe to deploy with zero downtime.

Tests

  • Brand/Logo component suite — 9 new Vitest tests cover the four logo components (a11y attributes, theme-token defaults, layout modes). Total frontend coverage is now 257 tests across 54 files.

TravStats v1.0.1

17 Apr 20:04

Choose a tag to compare

Security

  • Base images refreshed and patched — Builders moved from node:20-alpine to node:22-alpine; production stage moved from node:20-slim to node:22-bookworm-slim. The production image now also runs apt-get upgrade at build time so every rebuild pulls the latest Debian security feed on top of the base layer. Closes the Critical CVE-2026-6100 and eleven High-severity npm CVEs (tar, minimatch, cross-spawn) that Docker Scout reported against v1.0.0. Drop-in replacement — no schema, config, or runtime behaviour changes.
  • CI aligned with runtime — GitHub Actions now runs backend and frontend checks on Node 22 to match the container image.

v1.0.1-security-rc.1 (Release Candidate)

17 Apr 19:54

Choose a tag to compare

Security patch — Release Candidate

Docker Scout flagged the v1.0.0 image for one Critical (CVE-2026-6100, Debian) and eleven High npm CVEs (tar, minimatch, cross-spawn). This RC rebuilds the image with refreshed base layers and pulls all pending Debian security patches.

Changed

  • Base images bumped — Dockerfile builders moved from node:20-alpine to node:22-alpine; production stage moved from node:20-slim to node:22-bookworm-slim.
  • Debian security patches applied at build time — added apt-get upgrade -y --no-install-recommends so every rebuild pulls the latest Debian security feed on top of the base image.
  • CI aligned — GitHub Actions runs backend + frontend on Node 22 to match the production runtime.

Verification

  • Prod container runs on Node v22.22.2.
  • /health returns ok.
  • No schema or runtime config changes — drop-in replacement for v1.0.0.

Next

This RC is deployed on the reference environment for UAT. Promote to v1.0.1 once verified — it will retag the exact same GHCR image (no rebuild), mirror to Docker Hub, and replace :latest / :stable.

Image: ghcr.io/abrechen2/travstats:1.0.1-security-rc.1
Digest: sha256:d344388b584a6e4b0bf02de088064ffb8c3a7da9cd83c4a4fd28f7e92c724ac5
Branch: security/docker-scout-patch (commit 2edcaad)

TravStats v1.0.0

17 Apr 16:59

Choose a tag to compare

TravStats v1.0.0 — the first public release ✈️

Six months of daily use, 22 resolved pentest findings, a full switch to DB-backed config and a thousand tiny UX tweaks later, TravStats goes stable. One container, one environment variable, the setup wizard handles the rest. No telemetry, no account on anyone else's server, no ads — your flights stay on your own machine.

"I just wanted to know where I've been — without handing that to a company."
That's the whole thesis. TravStats is for 1–10 users who want their travel history to outlive a startup's pivot.


What you can actually do with it

✈️ Track flights the way that fits you

  • Manual entry with categories, tags, up to 50 travel companions, cost + currency
  • Boarding-pass scanner — QR, PDF417 and OCR fallback with automatic airline/airport resolution
  • Email & PDF import — plain text, HTML, Outlook .msg, .eml, with optional local LLM parsing via Ollama (gemma3:12b benchmarks 100% accuracy on our test corpus)
  • Five flight states — flown · scheduled · cancelled · historical · duplicated, each with its own form, validation and stats treatment
  • Duplicate detection with a confirmed "save anyway" escape hatch when you actually did fly the same number twice in a day

🗺️ Six map modes on one canvas

Routes · Heatmap · Hexagon (3D) · 3D Columns · animated Trips · 3D Globe. Built on deck.gl 9 and MapLibre 5 through a shared WebGL context, so switching visualisations doesn't flash. Scheduled flights render as cyan arcs and are excluded from stats until they're flown.

📊 Statistics that actually tell a story

Year-over-year comparison, seat/class distribution, top routes, cost tracking, airline loyalty breakdown. Export a PDF year report for your own records, or download a vintage-passport-style PNG certificate with your totals to share.

🏆 58 achievements across 5 categories

Explorer · Distance · Collector · Elite · Special — plus Planner and Survivor tiers for travellers who cancel nothing, and those who survive them. Thresholds are tuned so v1 users don't unlock everything in the first week.

🤖 Automation that never leaves your LAN

  • Automatic flight-data lookup via AirLabs (primary), OpenSky OAuth and Aviationstack fallback — with live gate, terminal and actual-times while the flight is in the air
  • Pending-update inbox — nothing changes without your approval, and every suggestion shows its statistics impact before you accept it
  • Historical enrichment scheduler backfills gate/terminal/aircraft data for past flights
  • 24 h / 2 h pre-departure reminders via SMTP (off by default)
  • Automated database backups with retention + optional WebDAV sync to Nextcloud, HiDrive or any WebDAV endpoint
  • Runtime auto-update checker that pings GHCR for a newer image and lets an admin redeploy with one click

🔐 Security you can audit yourself

  • JWT stored in an HttpOnly, SameSite=Strict cookie — no Bearer fallback, no localStorage
  • 15 rate limiters on auth, external-API-backed routes and admin exports (LAN traffic is skipped so the admin dashboard doesn't throttle itself)
  • Zod validation on every input endpoint, Prisma-parameterised queries, Helmet CSP, server_tokens off
  • 22 pentest findings (2 CRITICAL, 5 HIGH, 8 MEDIUM, 7 LOW) surfaced and mitigated across the beta — reproducible verification commands in SECURITY.md
  • JWT + encryption keys auto-generated on first boot and persisted inside the data volume at /app/data/secrets/ — no secret in your .env

🐛 Friction-free bug reports

One button in the top nav copies an anonymised diagnostic bundle (log tails, flight-state aggregates, settings sans credentials) to your clipboard and opens a pre-filled GitHub Issue Form with your version populated. Two clicks from "something's weird" to "here's a useful report".


Installation

Docker Compose (bundled Postgres):

curl -O https://raw.githubusercontent.com/Abrechen2/TravStats/main/docker-compose.prod.yml
echo "DB_PASSWORD=$(openssl rand -base64 32)" > .env
docker compose -f docker-compose.prod.yml up -d
open http://localhost:3000/setup

Unraid: Community Apps templates live at Abrechen2/docker-templates — install travstats-db first, then TravStats, set the password, open /setup.

Images: ghcr.io/abrechen2/travstats:1.0.0 · docker.io/abrechen2/travstats:1.0.0

Full install guide with screenshots: README.md · Unraid step-by-step: docs/unraid/README.md


Zero-config is the default now

For a default Docker install, the only thing you set in a file is DB_PASSWORD. Instance name, public URL, user cap, registration mode, API keys, Ollama endpoint + model, backup schedule, WebDAV — everything is captured by the first-run setup wizard or configured from the admin UI afterwards. The setup form itself is just username + password + confirm; sensible defaults are applied silently and are editable anytime.

This was the single biggest change going into 1.0: nine new admin_settings columns took the place of nine environment variables, and the runtime reads from there first. Pre-1.0 installs keep working — the old env vars are honoured as a one-time fallback until an admin saves from the UI.


Breaking changes from 0.x

  • Docker registry: images still live on GHCR (ghcr.io/abrechen2/travstats) as the primary target. Starting with 1.0, X.Y.0 releases are also mirrored bit-identically to Docker Hub (abrechen2/travstats) for discovery. Pre-release candidates and patches stay on GHCR only.
  • Default LLM model is now gemma3:12b (was qwen2.5:7b). Change it in Admin → Parser after login.
  • Parser-feedback collection removed. The /admin/parser-feedback/* endpoints and the FeedbackAnalytics admin tab are gone. The separate ParseTrainingLog-backed hit-rate dashboard is unchanged.
  • Install footprint: one volume now covers the app data, backups and auto-generated secrets (previously a dedicated /app/secrets mount). Pre-1.0 installs are auto-migrated at container boot.
  • Instance-level ENV vars are now optional. INSTANCE_NAME, MAX_USERS, ALLOW_REGISTRATION, FRONTEND_URL, WEBDAV_* are still read once as a fallback, then superseded by the DB value the first time an admin saves from the UI.

What's next

The v1.x roadmap in ROADMAP.md is driven by real-world use:

  • v1.1 Cruises module — track cruise segments alongside flights, same interactive map
  • v1.2 Trip planner — draft upcoming travel with budget and gear checklists
  • v1.3 CO₂ footprint tracking + compensation suggestions
  • v1.4 Social sharing — optional opt-in "my travel year" cards
  • v1.7 PWA — installable web app, offline flight entry sync

Priority shifts with feedback. Open an issue or discussion — we read all of them.


About this release

TravStats was built solo over six months — as a personal travel-history tool first, hardened through daily use on a homelab, and carried through a full black-box pentest before going public. v1.0.0 is the first release you're seeing because it's the first one I'd hand to someone else without caveats.

If it ends up useful to you, the nicest ways to say thanks are:

  • ⭐ Star the repo on GitHub — helps other self-hosters find it
  • Tell the community — post on your favourite homelab/self-hosted subreddit or Mastodon instance
  • Open a pull request with the airline template you wish TravStats already shipped
  • A small donation via PayPal keeps the AirLabs quota topped up for community users who share the key

Safe travels.

— Dennis

v1.0.0-rc.7 (Release Candidate)

17 Apr 16:04

Choose a tag to compare

Pre-release

Release Candidate 7 for v1.0.0

Supersedes v1.0.0-rc.6 with three UAT-driven polishes.

Image version surfaced in the UI

The Admin → System-Info card and Einstellungen → About used to show the stable base version (1.0.0) even for RC builds, which made it impossible to tell which RC was running. Fixed in two places:

  • Dockerfile writes the build-arg VERSION (e.g. 1.0.0-rc.7) into /app/VERSION at image build time.
  • New public GET /api/v1/version endpoint. The About section fetches from it and falls back to the bundled package.json while the request is in flight.

No-LLM email-import warning

When the admin hasn't wired up an Ollama endpoint, the email import tab now shows an amber banner before the drop zone: "Regex parser is inaccurate; can't split multi-flight emails. Recommended: Admin → Parser → Ollama, or manual entry." Driven by a new public GET /api/v1/parser-capabilities endpoint.

Correct Ollama model default

The admin parser UI placeholder flipped from qwen2.5:14b to gemma3:12b (the model that actually benchmarks 100% accuracy on the TravStats test corpus).

Docker images

ghcr.io/abrechen2/travstats:1.0.0-rc.7
docker.io/abrechen2/travstats:1.0.0-rc.7

Digest: sha256:69c420a4a3ba11360904713f142b4329ea1399917cb6bceb20819262200835f0

How to update an rc.6 install

Unraid → Docker → TravStats → Edit → change Repository tag 1.0.0-rc.6 to 1.0.0-rc.7 → Apply.

v1.0.0-rc.6 (Release Candidate)

17 Apr 15:46

Choose a tag to compare

Pre-release

Release Candidate 6 for v1.0.0

Supersedes v1.0.0-rc.5 — UI decluttering and Unraid install simplification.

Dashboard onboarding ripped out

The onboarding checklist sat as a flex-row between the navigation bar and the map. On shorter viewports it pushed the map (and all its overlay buttons — add flight, filter, visualisation picker) off-screen; the user could reach its own checkboxes, but nothing else. Fully removed: OnboardingGuide, OnboardingStep, ContextualHint, HomeAirportOnboardingBanner, the /settings/onboarding-state route, the OnboardingState interface, the onboarding i18n namespace, and all state plumbing in Dashboard / Achievements / Advanced Stats. -736 lines net.

Unraid install simplified — no custom network required

Both templates back on Docker's stock bridge. travstats-db publishes its port to the Unraid host (default 5432, configurable), and TravStats reaches it via host.docker.internal:5432 thanks to an --add-host=host.docker.internal:host-gateway in its ExtraParams. First-time install now: create DB container → create TravStats container → done. No docker network create pre-step.

Docker images

ghcr.io/abrechen2/travstats:1.0.0-rc.6
docker.io/abrechen2/travstats:1.0.0-rc.6

Digest: sha256:9c53b04d376246c76ec226f0efbe600c5d64850b220fabf6030e3cf19488097b

How to update an rc.5 install

Unraid → Docker → TravStats → Edit → change Repository tag 1.0.0-rc.5 to 1.0.0-rc.6 → Apply. Appdata stays, DB stays, JWT stays (persisted in /app/data/secrets/). Migrations run on first boot.

v1.0.0-rc.5 (Release Candidate)

17 Apr 15:12

Choose a tag to compare

Pre-release

Release Candidate 5 for v1.0.0

Supersedes v1.0.0-rc.4 with three user-visible polishes uncovered while walking through the Unraid install on a clean box.

Setup wizard reduced to admin credentials

Instance name, public URL, user cap and registration mode are dropped from the first-run form. They're filled with sensible defaults (public URL auto-captured from window.location.origin) and editable under Admin → Instanz anytime. First-run is now literally username + password + confirm.

Global rate limit skipped on LAN

The /api/ catch-all limiter no longer throttles loopback, Docker-bridge and RFC 1918 ranges. Self-hosted installs (Unraid, homelab compose) stop 429-ing themselves during the dashboard-polling that follows a fresh install. Public-facing deployments still enforce the 10 000/15 min cap.

Dead developer-mode toggle removed

The old Parser-Entwicklermodus switch under Einstellungen → Developer Options hadn't actually gated anything since the parser page started checking user.isAdmin directly. Fully removed — route, components, confirmation dialog, two user_settings columns (via Prisma migration).

Unraid template polish

  • Both containers now default to the user-defined travstats-net bridge (Unraid creates it on first install); Docker's stock bridge has no container-name DNS, which was biting installers.
  • WebUI URL uses [PORT:80] so the button follows whatever host-port is set, not the default 3000.
  • DATABASE_URL unmasked with a clearer 'Database URL' label + step-by-step description.
  • Companion travstats-db template added so PostGIS installs in one click.

Docker images

ghcr.io/abrechen2/travstats:1.0.0-rc.5
docker.io/abrechen2/travstats:1.0.0-rc.5

Digest: sha256:14568918d09e0981c91e2a5962285d7500c729785fdc619b2d06733c220ae4b9

v1.0.0-rc.4 (Release Candidate)

17 Apr 14:17

Choose a tag to compare

Pre-release

Release Candidate 4 for v1.0.0

Supersedes v1.0.0-rc.2 and v1.0.0-rc.3 with the install surface consolidated and two follow-up fixes.

Install-surface consolidation

  • One volume instead of two. Secrets (JWT, AES-GCM encryption key) moved out of the dedicated /app/secrets mount into /app/data/secrets/ inside the main data volume. Both compose and Unraid now need a single /app/data mount; pre-1.0 installs are migrated automatically at boot so existing key material stays in use.
  • Unraid template slimmed down to DATABASE_URL + TZ only. OLLAMA_URL, COOKIE_SECURE and CORS_ORIGIN dropped from the template — Ollama is configured from the admin UI, cookie-secure auto-detects from X-Forwarded-Proto, and CORS defaults to same-origin behind a proxy. All three still honour environment overrides for exotic setups.

Fixes since rc.3

  • Docker entrypoint writes JWT to /app/data/secrets/jwt.secret directly (rc.3 still wrote to /app/secrets, bypassing the Node-side resolver).

CI right-sized for a solo-dev workflow

  • ci.yml runs on push to Main (previously only via workflow_call).
  • Redundant docker-build.yml removed; the release.yml Docker job dropped too. Images are built locally by the /deploy skill and pushed straight to GHCR.

Docker images

ghcr.io/abrechen2/travstats:1.0.0-rc.4
docker.io/abrechen2/travstats:1.0.0-rc.4

Digest: sha256:8176971472ab95655aa029035f561cd1fc4e7f0898c9b8fef7c53b57adaa7220

Unraid test install

The Community-Apps template is at docs/unraid/travstats.xml. Install PostGIS from CA first, add TravStats from the template, set DATABASE_URL to postgresql://flights:<password>@travstats-db:5432/flights, open /setup.

v1.0.0-rc.2 (Release Candidate)

17 Apr 13:43

Choose a tag to compare

Pre-release

Release Candidate 2 for v1.0.0

Follow-up to v1.0.0-rc.1 with the following fixes layered on top of the same v1 feature set. Matches the image that is currently deployed on flighttest for UAT.

Security

  • npm audit fix patches four moderate-severity transitive CVEs. Lockfile-only; no behaviour change.
    • GHSA-r4q5-vmmm-2653follow-redirects leaks custom auth headers on cross-domain redirects (via axios, frontend + backend)
    • GHSA-39q2-94rc-95cpdompurify ADD_TAGS bypasses FORBID_TAGS (via jspdf, frontend)
    • GHSA-j452-xhg8-qg39protocol-buffers-schema prototype pollution (via maplibre-gl > pbf, frontend)
  • Hardcoded internal LAN IP in the Ollama URL UI placeholder replaced with http://localhost:11434 (German + English).
  • Private attack-roadmap doc removed from the public repo (present in v1.0.0-rc.1 as .private/PENTEST_FINDINGS.md); the full git history has been rewritten to remove it everywhere.

Docs

  • Legacy root unraid-template.xml deleted (pointed to Docker Hub with eight legacy ENV vars that are now DB-stored).
  • docs/unraid/travstats.xml now points at the Docker Hub mirror for native Unraid Community Apps discovery.
  • backend/.env.example slimmed from 151 to ~60 lines — only the five runtime knobs remain; everything user-configurable post-install is marked as UI-configured.

Docker image

ghcr.io/abrechen2/travstats:1.0.0-rc.2

Digest: sha256:0fbdf2f122ba41157885943e35e8dfb4d52ee0ea55c958cf5eb3388b5a402d15

On green UAT

The tested image will be retagged bit-identically to :1.0.0 / :latest / :stable on GHCR, mirrored to Docker Hub as abrechen2/travstats:1.0.0 / :latest / :stable, and a proper v1.0.0 release published.