Add Done button, tablet controls, super admin management, midnight strike reset, and activity log improvements

- Done button snaps block to full duration, marks complete, logs "Marked Done by User"; Reset after Done fully un-completes the block
- Session action buttons stretch full-width and double height for tablet tapping
- Super admin: reset password, disable/enable accounts, delete user (with cascade), last active date per user's timezone
- Disabled account login returns specific error message instead of generic invalid credentials
- Users can change own password from Admin → Settings
- Strikes reset automatically at midnight in user's configured timezone (lazy reset on page load)
- Break timer state fully restored when navigating away and back to dashboard
- Timer no longer auto-starts on navigation if it wasn't running before
- Implicit pause guard: no duplicate pause events when switching already-paused blocks or starting a break
- Block selection events removed from activity log; all event types have human-readable labels
- House emoji favicon via inline SVG data URI
- README updated to reflect all changes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 00:08:15 -08:00
parent f645d78c83
commit ff9a863393
18 changed files with 768 additions and 108 deletions

View File

@@ -31,16 +31,19 @@ async def compute_block_elapsed(
for e in tick_events:
if e.event_type == "reset":
elapsed = 0.0
last_start = e.occurred_at
last_start = None
elif e.event_type in ("start", "resume"):
last_start = e.occurred_at
elif e.event_type == "pause" and last_start:
elapsed += (e.occurred_at - last_start).total_seconds()
last_start = None
if last_start:
running = last_start is not None
if running:
elapsed += (datetime.utcnow() - last_start).total_seconds()
is_paused = bool(tick_events) and tick_events[-1].event_type == "pause"
# is_paused is True whenever the timer is not actively running —
# covers: explicitly paused, never started, or only selected.
is_paused = not running
return int(elapsed), is_paused
@@ -70,8 +73,9 @@ async def compute_break_elapsed(
elif e.event_type == "break_pause" and last_start:
elapsed += (e.occurred_at - last_start).total_seconds()
last_start = None
if last_start:
running = last_start is not None
if running:
elapsed += (datetime.utcnow() - last_start).total_seconds()
is_paused = bool(tick_events) and tick_events[-1].event_type == "break_pause"
is_paused = not running
return int(elapsed), is_paused