- refactored bulk remove trigger flow: removed global
windowevent dependency and replaced it with explicit Zustand request signaling (bulkRemoveRequestId), reducing cross-component coupling and preventing event-listener drift - stabilized optimistic table updates by moving from full local data copies to per-entry overlay patches, reducing risk of state desync between React Query cache and UI
- fixed import modal error handling: switched from brittle
JSON.parse(err.message)to structuredApiErrorparsing (status,body,retryAfterSeconds) - fixed import overwrite flow reliability by correctly handling backend
OVERWRITE_REQUIREDpayload (years) without dropping validated import payload state - fixed backend entry update/create validation gaps:
name,comment,sort_index, and month values are now validated and normalized before DB write/encryption - runtime image now drops root privileges (
USER node) before starting Mopay backend - fixed non-root startup compatibility for existing bind-mounted DB volumes by adding runtime entrypoint ownership repair before dropping privileges
- hardened runtime entrypoint DB permission checks with explicit
SQLITE_READONLYdiagnostics when host bind-mount permissions block writes
- added table query-state composition hook
useTableQueryStateto centralize entries/groups/tags reads and optimistic entry overlays - added reusable table layout rows (
TableHeaderRow,TableTotalRow) to separate static rendering concerns from interaction logic inTableView
- performance-oriented
TableViewarchitecture update:- removed duplicated local mirrors for groups/tags query data
- narrowed store subscriptions in hot paths and reduced broad state reads
- converted core row/group render blocks to memoized components
- kept optimistic reorder/group/name/month updates while reducing full-list rewrites
- maintainability improvements:
- introduced typed table models in
frontend/src/components/table/types.ts - reduced
TableViewresponsibilities by moving query/overlay logic and UI-only rows to dedicated modules - import UI now uses a dedicated error classifier for
OVERWRITE_REQUIRED,IMPORT_IN_PROGRESS,SQLITE_BUSY, auth/session errors, and encryption-key mismatch responses - backend entries now use shared normalizers for payload validation (
normalizeEntryName,normalizeEntryComment,normalizeEntrySortIndex,normalizeEntryMonthValue) to prevent invalid encrypted values from being persisted - hardened compose defaults with
security_opt: no-new-privileges:truefor both GHCR and local-build deployment flows
- introduced typed table models in
- documentation update:
- refreshed
README.mdanddocs/ARCHITECTURE.mdto reflect current grouping, import scope, PIN session model, release check behavior, and deployment/security notes
- refreshed
- UI improvement: introducing icons instead of text
- lockout UX fix: the PIN attempt that crosses the failure threshold now returns immediate
429 LOCKOUT(withRetry-After) without requiring page reload/new tab
- added optional webhook-based security alerting for high-volume failed PIN attempts (
SECURITY_WEBHOOK_URL+ alert thresholds) - Pin Guard: implemented server-side PIN sessions (issued on
/api/pin/verify, revoked on/api/pin/logout) with sliding expiration and in-memory token store
- Pin Guard: added brute-force protections for PIN verification:
- per-IP rate limiting (minute + burst window)
- progressive lockout after repeated failures
Retry-Afterresponse header on temporary blocks- minimum response delay to reduce timing/oracle value
- Pin Guard: added security audit logging for auth denials, PIN failures/successes, and rate-limit/lockout events
- UI improvement: introducing icons as replacement of "named actions"
- Pin Guard: security hardening:
- backend API now requires an active PIN session token (
X-Mopay-Session) for all protected/api/*routes - CORS hardening: wildcard reflective CORS removed; cross-origin API access now opt-in via
CORS_ALLOWED_ORIGINS - frontend lock flow now revokes backend session token (not only UI state), and PIN unlock invalidates queries to refresh secured data immediately
- backend API now requires an active PIN session token (
- UI corrections - fields in the incomes and expense tables dont resizing entire row while in input mode
- Improvement of mobile view and introducing compact view to simplify using on mobile screens
- Improvement in UI animations - eliminating places where loading content was causing "blinking" - now transitions are smooth
- PWA cache improvement - now properly recognise new releases without enforcing page reload
- Github workflow fix:
devreleases build+push to:dev_latest(including versioned:dev_<version>) without touching:latestmainreleases build+push to:latest(including versioned:<version>)
- 🔥 Introducing dash "-" in value fields as "N/A" - gives option to ignore cell in calculations (avg, reports/stability)
- import now preserves tag notes from Excel and maps tag colors (unsupported/missing colors with notes fall back to grey)
- supported colors (they are the main colors form excel color picker):
- #D9D9D9 → grey
- #FF0000 → red
- #FFC000 → orange
- #92D050 → green
- supported colors (they are the main colors form excel color picker):
- maintenance cleanup removes orphaned data on startup (logged)
- improving Import and export menu UI - more consistent, less buttons
- import overwrite preflight: shows exact years to overwrite and prevents accidental skips when DB changes between validate and import
- backend now handles
SQLITE_BUSYmore gracefully (busy_timeout + friendly errors) and serializes imports to avoid concurrent overwrite conflicts
- 🔥 New feature - now incomes and expenses can be moved into collapsible groups. To make life easier, export and import feature now supports grouping also.
- update notification improvement - release info moved to Setting menu, update notification visible on navigation bar only when new update released
- UI improvements ended in recreating "Edit mode" menu - all operations in one place
- minor UI improvements mainly focusing on paddings and properlu using UI's space
- 🔥💥 Import feature has been implemented - Import flow with template download, validation, year overwrite confirmation, and progress/status feedback.
- security fix for exceljs library and its dependencies
- security fix for frontend and backend based on npm audit results
- addjusting/improving mobile operations on Tagging feature
- UI improvement - main table has more narrow entries heights
- compact mobile view - better adjustments of spacings and improvements
- compact na full view improvments
- settings menu improvement and reorganization - more compact in "settings style" with toggles
- correcting year deletion behavior (now with confirmation)
- relocating operation confirmation (for year removal and add) - no more inconsistent window
- new year picker area - now marking year for deletion shows visible "red" as warning also list of years builds horizontally to safe space/optimize window
- improvements in compacted modal layout on mobile/responsive screens (less padding/spacing, desktop untouched)
- Version Awareness now consistently picks the newest release per channel (dev/main) instead of relying on API order
- release workflow tags dev builds improvements (devN and latest at the same time)
- backend/frontend version awareness now detects whether the instance runs on
main/latestordev_latestand only suggests upgrades from the matching branch - polished the settings modal lock button and “Working on year …” caption to align with the rest of the UI
- small dropdown menus corrections - improving UI clarity
- small correction to the implemented tagging feature
- Added structured tags per entry/month with Tag Builder mode (color highlight + hover details).
- Introduced backend
entry_tagstable, migration, and/api/tagsendpoints. - Updated UI (TableView, Edit menu) plus docs (
ARCHITECTURE.md). - Excel export now reflects tag colors and embeds tag notes as spreadsheet comments.
- tightened year dropdown menu/button sizing and made tables stretch correctly across months
- removing hover tip on VA feature
- normalizing year selector style in mobile view
- correcting tag string building
- adjusting style of VA feature (Version awareness)
- adjusting handling default year selector in "missing-cache" scenario
- Relocation of Version awareness feature from footer are to banner area for better visibility
- Restyled the add-to-home-screen notification to align with the core UI buttons/fonts and added a Skip action with session persistence.
- Docker build now injects the release/tag into the image, exposing
/api/metawith backend/app version data. - UI footer displays the running Mopay version and pings GitHub Releases to show an “Update available” badge when a newer tag exists.
- Translated remaining Polish UI strings (PWA install banner, offline mode bullet) to English.
- Updated PWA manifest description and encryption notice text files to match the new copy.
- README screenshot captions now use English labels (Savings, Reports, Settings).
- Added transparent encryption for all monetary data:
- monthly values in entries (
Jan–Decfor incomes/expenses), - savings goals target values,
- savings items values,
- PIN is now stored as a salted hash wrapped in encryption.
- monthly values in entries (
- Mopay now requires an encryption key via
APP_ENC_KEY:- generate a key, for example:
openssl rand -base64 32, - set it in your Docker config, e.g.
APP_ENC_KEY=base64:...indocker-compose.yml.
- generate a key, for example:
- Existing databases:
- on first start with a valid
APP_ENC_KEY, Mopay encrypts all existing numeric values in-place, - the app shows a one-time in-app notice that your data has been encrypted.
- on first start with a valid
- Missing key:
- if
APP_ENC_KEYis not set, the backend does not start and logs:Error: APP_ENC_KEY environment variable is required for Mopay to start. - running Mopay without encryption is no longer supported.
- if
- Changed key (mismatch with existing data):
- Mopay detects when
APP_ENC_KEYdoes not match the key used to encrypt the current database, - access to data is blocked and a clear message is shown:
Your APP_ENC_KEY has been changed! Revert to previous encryption key to keep your data., - in the UI you can either:
- restore the previous
APP_ENC_KEYin your Docker config to keep all data, or - wipe all stored data and start fresh with the current key (requires a two-step confirmation in a red “Confirm reset” dialog).
- restore the previous
- Mopay detects when