Releases: TomSchmidtDev/visualizer-lite
Release list
v1.20.0 — Roaster/Bean Combobox
Added
- Combobox input for the Roaster/Bean fields in Shot Edit: The "Röster" (
beanBrand) and "Bohne" (beanType) fields on the Edit Shot page now combine free-text entry with an inline suggest-append (browser-address-bar style, accepted with the right arrow key when the caret is at the end of the text) and a substring-filtered, keyboard- and mouse-navigable dropdown of previously used values, sourced from the existing/api/search/suggestionsendpoint. No backend change was needed; typing an unknown value still saves it as free text exactly as before.
Fixed
- Dependency resolution: a duplicate
react/react-dominstall inpackage-lock.json(root vs.packages/web) that could break the frontend's React rendering has been deduped.
v1.19.1 — Decaid Import Support & Port Fallback
Added
- Decaid import support: The Direct Machine Import feature (Settings → Data) now also auto-detects Decaid alongside de1app and Decenza, and imports shots accordingly — no manual switch needed when changing tablet software. Decaid's shot history REST API (
/api/v1/shots) is fully paginated, so the entire history is imported regardless of size, and a new parser converts Decaid's row-oriented measurement snapshots into the same shot format used internally for de1app/Decenza imports. Coffee name, roaster, and barista are read from the shot's workflow context when present, and Decaid's device-reported gravimetric flow rate (scale.weightFlow) is mapped to the same channel de1app/Decenza expose. The connection test reads the shot count from a single request rather than paging through the full history, and listing/preview stop paging early once results (sorted newest-first) fall before the requested start date, instead of always walking the entire history. - Automatic port fallback for Direct Machine Import: de1app/Decenza and Decaid conventionally listen on different default ports (8888 vs. 8080). If the configured machine URL doesn't respond, Visualizer Lite now automatically retries the other port, and — once that succeeds — saves the corrected URL so later connections go straight to the working port.
Fixed
- Dependency security patches:
fastifybumped from 5.8.5 to 5.12.1 (security release; fixes GHSA-w2qp-rph6-63g4 and GHSA-3m5p-2c4r-xxw2).fast-uri(transitive, viaajv/fast-json-stringify) bumped from 3.1.5 to 3.1.7 within existingpackage.jsonranges — fixes four GHSA advisories (host confusion via skipped IDN canonicalization, SSRF via malformed IPv6 normalization, SSRF via repeated hostname percent-decoding, host confusion via percent-encoded scheme normalization).
Known issue (deferred):
deepmerge-ts<8.0.0 (GHSA-ggr8-5vv4-36mx, stack exhaustion via recursive merge), pulled in byprisma's (devDependency, CLI only)@prisma/config. No 6.x release fixes this — the only upstream fix ships in Prisma 7/8, a major version with its own breaking-change migration, andlatestcurrently points at an 8.0.0 release candidate, not a stable release.prismais dev-only and pruned from the production image; the vulnerable merge only ever processes local, trusted config/schema files at deploy time, never user-supplied input, so real-world exploitability in this deployment is effectively nil.
v1.18.1 — Decenza Import Support & Security Patches
Added
- Decenza import support: The Direct Machine Import feature (Settings → Data) now auto-detects whether the configured machine URL is running de1app or Decenza, and imports shots accordingly — no manual switch needed when changing tablet software. Decenza's shot JSON is consumed directly (Visualizer-compatible format, no conversion needed).
- Parser: enjoyment/notes from JSON-format shots:
parseDecentShotnow readsmeta.shot.enjoymentandmeta.shot.notesfor JSON-format (v2) shots, benefiting both Decenza imports and any future de1app v2 JSON shots.
Changed
- The Direct Machine Import connection status now shows which app was detected (e.g. "Connected — Decenza — 42 shots available").
Fixed
- Dependency security patches:
@fastify/static9.1.3 → 10.1.2 (fixes route-guard bypass and authorization bypass via non-canonical URL paths),react-router-dom7.6.2 → 7.18.2 (fixes denial-of-service via inefficient route matching, open redirect, and other advisories affecting <7.18.0). Transitive dependenciesfind-my-way,fast-uri,postcss, andbrace-expansionrefreshed to their patched versions within existingpackage.jsonranges.
Note: One Dependabot alert remains open — a React Router CSRF advisory (GHSA-qwww-vcr4-c8h2) fixed only in v8, which requires migrating off the
react-router-dompackage entirely. The advisory states it only affects apps using the unstable RSC APIs, which this app does not use.
v1.17.0
What's new
Added
- Pre-commit i18n validation:
scripts/check-i18n.mjs+ husky v9 pre-commit hook validates all statict('...')calls against bothde.jsonanden.json, and checks that both files have identical key sets. Dynamic keys (template literals) emit a warning but do not block the commit. Calls with extra arguments (t('key', { count })) are also validated.
Fixed
- Missing translation keys:
shots.profileTitleandshots.grinderModelwere used inSearchBar.tsxfilter chips but were absent from bothde.jsonanden.json(discovered by the new validation script).
See CHANGELOG.md for the full history.
v1.16.0
What's new
Fixed
- i18n — complete DE/EN separation: All remaining hardcoded German/English strings in UI components replaced with
t()calls: context window labels in AnalysisPanel (7 T,30 T,Alle→ translated), Roaster tab label, theme toggle, filter chips (Röster:,Profil:,Mühle:), shot title and error fallbacks in ShotCard, ShotDetail, ShotCompare, Stats - Password change form: New password must now be entered twice for confirmation; mismatch is caught client-side before the API request
Added
- Synology NAS installation guides: Step-by-step guides in English (docs/synology-installation.md) and German (docs/synology-installation.de.md) — Task Scheduler method (no SSH, with screenshots) and SSH method
Changed
- README screenshot gallery: Updated with new high-quality screenshots covering shot list, extraction curves, statistics dashboard, shot comparison (overlaid + split), AI analysis (Barista + Roaster perspectives), and all settings tabs
- Architecture section: Added link to AI analysis documentation (
docs/ai-analysis.md)
v1.15.0 — AI Analysis: Context Transparency, Timing, i18n
What's new in v1.15.0
New Features
- Historical context visible: New first metadata row shows how many shots were passed to the AI as a comparison baseline (avg pressure, avg flow, avg temperature, matching tier)
- Collapsible details: Timing + tokens/cost behind a ▸ toggle; row 1 (timestamp + context) always visible
- Configurable context window: 7d / 30d / 90d / All in Settings → AI Analysis
- Configurable matching thresholds: Tier-1 minimum and minimum shot count are adjustable
- Full i18n: All AI analysis strings translated in DE + EN
Fixes
- AI now responds in the configured language (explicit language instruction added to all system prompts)
- Accept-Language header is now parsed correctly (including German as secondary language)
- Timing fields now correctly returned from cache path
- Tier-1 threshold raised to ≥ 10 shots (prevents misleading small-sample results)
- Stale "Analyst" instruction removed from default prompt
Upgrade
Standard Docker Compose update — no manual DB migrations required (run automatically on startup).
v1.13.2
Bug Fix
AI analysis crash: React Error #31 when model returns objects instead of strings
The optimised analysis mode occasionally made the model return structured objects like {"phase": "Extraction", "finding": "...", "action": "..."} instead of plain strings in the barista/roaster arrays. React then crashed with Error #31 ("Objects are not valid as a React child").
Root cause: The optimised system prompt showed {"barista":[...],...} without an explicit string example — the model interpreted the instruction to "reference phase names" as a reason to build structured objects.
Fix (three layers):
- All four system prompts (standard/optimised × DE/EN) now show string examples and explicitly state: "Each entry must be a plain text string — NOT an object, NOT nested JSON."
- New
normalizeAnalysisArray()function converts any object entries to readable strings (phase — finding — action) at the API boundary — runs on both fresh responses and cached results, so already-stored bad analyses are fixed on next view without regenerating. AnalysisPanelrenders defensively as a last-resort fallback.
v1.13.1
Bug Fix
AI analysis: profile- and bean-scoped historical baseline
Previously the historical context (avg pressure, flow, temperature shown at the bottom of the analysis prompt) was computed from all shots in the time window — mixing completely different profiles. A Turbo shot and a Blooming Flow shot have fundamentally different extraction characteristics, making cross-profile averages meaningless.
New behaviour (tiered matching):
- Same profile & bean —
profileTitle+beanBrand+beanTypemust all match (most accurate baseline) - Fallback: same profile — if fewer than 2 shots match on profile+bean, match by profile only
- No context — if no
profileTitleis set, or fewer than 2 matching shots exist
The prompt now labels which tier matched, e.g.:
History (14 shots, same profile & bean): pres=9.1bar · flow=1.9ml/s · temp=93.2°C
v1.13.0
What's New
Settings tabbed navigation — The Settings page is now organized into 4 labeled tabs with icons:
- 🎨 Ansicht — Language, Theme, Statistics
- 💾 Daten — DE1 Direct Import, Export, Database Info
- 🔒 Sicherheit — Password
- 🤖 KI Analyse — AI Analysis settings
The active tab is remembered across sessions.
v1.11.0 — AI Analysis Cost Tracking
What's new
AI analysis cost tracking
Each shot analysis now records the USD cost of input and output tokens at the time the analysis runs. Costs are displayed inline alongside the existing metadata:
04.06.2026, 14:23 • claude-haiku-4-5-20251001 • ↑ 1.234 / ↓ 567 Tokens • ↑ $0.000309 / ↓ $0.000071 = $0.000380
Why store costs explicitly? Prices change over time — storing costs at analysis time means the historical record stays accurate.
How pricing works: Live prices are fetched from the OpenRouter API and cached in-memory for 24 hours. If the API is unreachable, a hardcoded fallback table is used. If pricing is completely unavailable, the analysis runs normally and costs are stored as null — the cost display is simply omitted.
Supported models: claude-haiku-4-5, claude-sonnet-4-6, claude-opus-4-8, gpt-4o-mini, gpt-4o (and any OpenRouter-listed model via direct ID).
Full Changelog: https://github.com/TomSchmidtDev/visualizer-lite/blob/main/CHANGELOG.md