Overrides PHP's default session.gc_maxlifetime (typically 1h on servers)
so sessions survive inactivity without relying on remember-me.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Clicking any past day dot now opens a stats panel (read-only for days older than yesterday)
- Entry form and delete buttons hidden for non-editable days
- Backend silently restores locked offsets (< yesterday) on PATCH, preventing backdated edits
- Negative buffer no longer shows green: badge and progress bar are amber/red when buf < 0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without always_remember_me: true, the RememberMeBadge was never
activated (Symfony default requires an explicit _remember_me field
in the request). Users were logged out after PHP session expiry
(~24min) instead of the intended 48h.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When Symfony returns a non-JSON body (empty or HTML) for a 401 on
protected API routes, res.json() throws a SyntaxError with no .status,
so showLogin() was never called. Now the HTTP status is preserved
through JSON parse failures so the 401 check works correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Thin animated bar at page top shows while any fetch is in-flight
- api() centrally redirects to login on 401 (except login endpoint)
- Logo click triggers manual goal refresh
- cursor:pointer on logo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rules:
- buf < 0 (behind) → always visible to catch up
- buf >= daily (more than a day ahead) → hidden
- otherwise → visible only if today not yet reached daily goal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Setting inp.value programmatically bypasses the oninput handler that
syncs addAmt[k], so addSet read 0. Firing the input event after fill
ensures the value is registered.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Max 10 pending invites per user (400 if exceeded)
- List sorted: pending → used → expired
- Expired invites hidden after 30 days
- Frontend shows error toast from server message on invite creation failure
- Tests: testInviteMaxTenPending, testInviteListSortOrder, testInviteExpiredHiddenAfter30Days
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If the accumulated buffer (buf) is >= the daily target, the goal is
already covered for today and doesn't need to appear in Quick-Buchen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 401 for unauthenticated requests
- 403 for authenticated non-admin users
- 200 with full user list for admin (looks up existing ADMIN_EMAIL user,
skips gracefully if not present in DB)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ADMIN_EMAIL env var controls who has admin access
- GET /api/admin/users returns all users (id, email, username, registered);
returns 403 for non-admins
- GET /api/me now includes is_admin flag
- Menu shows "Nutzer/Users/Użytkownicy" button for admins that opens a
table with name, email, and registration date for all users
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Registration failed visibly because the controller returned successfully
(user created, invite consumed) but the JS then called loadGoals() without
an authenticated session — causing a 401 that surfaced as an error to the user.
- Add Security::login() after user creation so the session is established
immediately, matching the documented "registers + auto-logs in" behavior
- Wrap user persist and invite consumption in a single DB transaction so
the invite token can never be consumed if user creation fails
- Add 12 integration tests covering auto-login, locale updates, logout,
partial goal updates, invite isolation, and various edge cases (110 assertions total)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- STRINGS object in app.js with all UI strings in de/en/pl
- tr() function, ldoc() locale-aware date formatting
- tpl() auto-translates data-t/data-ph/data-val attributes on clone
- app.html.twig: data-t attributes on all template static text, language picker in data menu
- locale CHAR(2) column on users table; GET /api/me returns locale; PATCH /api/me accepts locale
- setLocale() persists to API + localStorage; applyLocale() reads user.locale → localStorage → navigator.language
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Stopwatch ⏱ button appears in add/quick-book rows when sw >= 1s, fills input with floor(seconds)
- AppController passes md5 hashes of app.js/style.css to template for automatic cache-busting
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Stopwatch between logo and menu: tap to start, tap to stop, tap to reset
- AppController passes md5-based version hash for app.js and style.css to prevent browser caching issues
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SSH key copied to /var/www/.ssh/ for www-data GitHub access.
All git, composer, and console commands now run as www-data so
var/cache/ files are always owned correctly without a manual chown.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates rememberme_token table, drops legacy delight-im/auth tables,
aligns column types with Doctrine entity definitions. Adds UserReset
entity so users_resets is managed by Doctrine instead of raw SQL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Symfony 8 SPA with Doctrine ORM, Symfony Security, vanilla JS frontend.
Migrated from plain PHP (delight-im/auth + raw SQL) to full Symfony stack.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>