Each child is assigned a unique permanent tv_token on creation. The TV
dashboard URL (/tv/:tvToken) and WebSocket (/ws/:tvToken) now use this
token instead of the internal DB ID. Existing children are backfilled
on startup. README updated to reflect the change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a styled donation card at the top of the Admin page linking to the
Buy Me a Coffee page, with a short description encouraging support.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- Auto-create a locked "Meeting" subject for every user on registration
and seed it for all existing users on startup
- Meeting subject cannot be deleted or renamed (is_system flag)
- 5-minute corner toast warning on Dashboard and TV with live countdown,
dismiss button, and 1-minute re-notify if dismissed
- At start time: full-screen TV overlay with 30-second auto-dismiss,
automatic pause of running block, switch to Meeting block, and
auto-start of Meeting timer
- Web Audio API chimes: rising on warnings, falling at meeting start
- Update README with Meeting subject and notification system docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New /super-admin/login and /super-admin routes with separate auth
- Super admin can view all registered accounts and impersonate any user
- Impersonation banner shows at top of screen with exit button
- ADMIN_USERNAME and ADMIN_PASSWORD config added to .env and docker-compose.yml
- Fixed auth store: export setToken, clearToken, and setUser so they are
accessible from superAdmin store
- Updated README with super admin feature, new env vars, and setup notes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Starting break now pauses the main block timer (frontend optimistic +
backend implicit pause event recorded before break_start)
- Resuming/starting the main block while break is active pauses the
break timer and exits break mode on all clients including TV
- Timer display counts negative past zero so overtime is visible while
label stays "Done!"
- Fixed WS start handler incorrectly skipping break-mode clear when
restarting the same block; resume handler now also clears break mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The day progress bar no longer uses day start/end times (it uses block
durations instead), so the field is no longer needed.
Removed from: Admin UI, schedule store, schedule model/schemas/router,
session broadcast payload, dashboard snapshot, and startup migrations.
DB columns are left in place (harmless, no migration required).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both the TV and parent dashboard progress bars now calculate % complete
based on total scheduled block time vs. remaining block + break time,
so the bar only advances while blocks are actively being worked.
TV bar labels changed to "🟢 Start" and "Finish 🏁".
Parent dashboard shows first block's scheduled start time on the left
and a live estimated finish time (now + remaining block/break time) on
the right.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Admin: per-block "Break Time" checkbox + duration (min) setting; new
Break Activities section (global list, same pattern as Morning Routine)
- Dashboard: break timer section appears on blocks with break enabled;
Start/Pause/Resume/Reset controls work independently of the main timer
- TV: left column switches to amber break badge + countdown during break;
center column shows configurable Break Activities list
- Backend: break_time_enabled/break_time_minutes columns on schedule_blocks
(auto-migrated on startup); break_activity_items table + CRUD router;
break timer events (break_start/pause/resume/reset) stored as TimerEvents
and broadcast via WebSocket; break_activities included in dashboard
snapshot and session_update broadcast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rewrote Daily Sessions feature to describe the select/start/pause/resume/reset
workflow instead of the old auto-start-on-click behavior
- Added Dashboard Controls table documenting each button's condition and action
- Updated Activity Log to include reset events
- Added utils/timer.py to project structure
- Expanded WebSocket Events table with select, reset, prev_block_* fields
- Removed stale description of single-click auto-start block switching
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
applySnapshot checked serverElapsed > 0 before setting blockStartedAt,
so any block with 0 elapsed (just reset, or started within the same
second) was loaded with blockStartedAt = null — the timer appeared frozen.
The condition now only checks for a current_block_id; the isPaused flag
already handles the "selected but not started" case correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reset clears the current block's elapsed time to zero and immediately
starts the timer. A shared compute_block_elapsed() utility (utils/timer.py)
handles the elapsed calculation in both the sessions and dashboard routers,
and correctly treats "reset" events as zero-elapsed restart markers so
page reloads after a reset show accurate times.
Layout: Start/Pause/Resume/Reset are grouped on the left; End Day sits
on the right via justify-content: space-between.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blocks are now selected without auto-starting the timer. Clicking a block
makes it current (highlighted) but leaves it in a ready state. A "Start"
button (indigo) triggers timing for a fresh block; "Resume" appears for
previously-worked blocks; "Pause" remains while running.
Also fixes the sidebar duration label to show "Done!" when elapsed ≥ total
and "< 1 min" for sub-minute remaining time instead of "0 min".
Backend adds a "select" event type that records an implicit pause for the
previous block, updates current_block_id, and broadcasts is_paused=true
with prev_block_elapsed_seconds so the TV sidebar stays accurate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_broadcast_session was missing morning_routine in its payload, so the
TV dashboard's applySnapshot reset the list to [] whenever a session
started or updated via WebSocket. A page refresh was required to
restore the items since only the REST dashboard endpoint included them.
Now queries MorningRoutineItem via child→user_id and adds the list to
every session_update broadcast, matching the dashboard snapshot.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Morning Routine, strike event logging, block timer remaining,
and single-click block switching to features
- Remove /schedules as a separate route (schedule management is in /admin)
- Remove docker-compose.override.yml and alembic/ references (neither exists)
- Replace Database Migrations section with accurate description of
the create_all + idempotent ALTER TABLE startup approach
- Update project structure to show new model/router files
- Update setup steps and URL table to match current routes
- Add block_elapsed_seconds to WebSocket events table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a per-user Morning Routine item list that appears in the TV
dashboard Activities panel during the "Good Morning" countdown
(before the first block starts).
- morning_routine_items table (auto-created on startup)
- CRUD API at /api/morning-routine (auth-required)
- Items included in the public DashboardSnapshot so TV gets them
without auth
- Morning Routine section in Admin page (same add/edit/delete UX
as subject options)
- TV Activities column shows routine items when no block is active,
switches to subject options once a block starts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Record a StrikeEvent row whenever a strike is added or removed,
and surface them in the activity log timeline with timestamp,
child name, and whether the strike was added or removed.
- New strike_events table (auto-created on startup)
- children router records prev/new strikes on every update
- GET /api/logs/strikes and DELETE /api/logs/strikes/:id endpoints
- Log view merges strike entries into the timeline (red dot,
"✕ Strike added (2/3)" / "↩ Strike removed (1/3)")
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Block list on both dashboards now shows time remaining on each block's
timer (allocated duration minus elapsed) instead of total duration;
the active block counts down live every second
- Fix block switching requiring 2 clicks: replace separate pause+start
requests with a single start request; backend implicitly records a
pause event for the previous block atomically
- Export blockElapsedCache from store so views can compute per-block
elapsed for both running and paused blocks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Double-click fix:
- After awaiting the pause, optimistically set current_block_id and
isPaused on the store so the UI switches instantly. The subsequent
WS start event confirms the state without requiring a second click.
TV elapsed reset fix:
- The TV's local cache was empty for blocks paused before it connected,
so returning to those blocks showed 0 elapsed.
- Backend now computes the block's accumulated elapsed from previous
start/pause cycles and includes it as block_elapsed_seconds in the
'start' WS event payload.
- All clients (Dashboard and TV) now use this authoritative server value
instead of relying solely on the local cache.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, clicking a different block always fired 'start' which reset
elapsed to zero — returning to a block lost all accumulated time.
Store changes:
- Add blockElapsedCache (blockId → elapsed seconds) that persists across
block switches within a session
- On 'pause' WS event: write the block's total elapsed into the cache
- On 'start' WS event: restore elapsed from cache (0 if never worked)
- On applySnapshot: seed cache with server-computed elapsed for the
current block (so reloading preserves state correctly)
- Clear cache when the session ends
Dashboard selectBlock changes:
- Auto-pause the currently running block before switching to another
- Clicking the active block while paused now sends 'resume' instead of
doing nothing
- Clicking the already-running active block is a no-op
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On reload, the access token existed in localStorage so isAuthenticated
was true, but user.value was null — fetchMe was never called, so
authStore.timezone fell back to 'UTC' before the component mounted.
Router guard now calls fetchMe() when authenticated but user data is
not yet loaded, ensuring user preferences are available to all
auth-required pages on the first render after a reload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
applySnapshot was always setting isPaused = false and blockStartedAt =
Date.now() regardless of the actual timer state, causing a paused block
to appear running whenever the dashboard was reloaded.
- Add is_paused field to DashboardSnapshot schema
- Dashboard endpoint derives is_paused by checking whether the last
start/resume/pause event for the current block is a pause
- applySnapshot now reads is_paused from the snapshot instead of
resetting to false, and only sets blockStartedAt when the block is
actually running (not paused)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- On session start, insert a session_start TimerEvent so the action
appears in the activity log timeline
- Load DailySession.template via selectinload in the timeline query
so the template name is available without extra round-trips
- _to_timeline_out maps the template name into block_label for
session_start events, displaying as "Day started — Template Name"
- Add session_start to EVENT_META on the frontend (🏫 icon)
- Hide the Edit button for session_start entries since changing their
event type to a block-level action doesn't make sense
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `timezone` column to User model (VARCHAR 64, default UTC) with
idempotent startup migration
- Expose and persist timezone via PATCH /api/users/me
- Fix TimerEvent.occurred_at serialization to include UTC offset marker
(+00:00) so JavaScript correctly parses timestamps as UTC
- Add frontend utility (src/utils/time.js) with timezone-aware
formatTime, getHHMM, getDateInTZ, tzDateTimeToUTC helpers and a
curated IANA timezone list
- Add Settings section to Admin page with timezone dropdown; saves to
both the API and localStorage for the unauthenticated TV view
- Update Activity Log to display and edit times in the user's timezone
- Update TV dashboard clock to respect the saved timezone
- Update README: features, setup steps, usage table, WebSocket events
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New GET /api/logs/timeline endpoint joins TimerEvent with block/subject/session data
- New PATCH and DELETE /api/logs/timeline/{id} endpoints for editing/removing events
- LogView redesigned as a chronological timeline grouped by date
- Edit inline: timer events support type + time correction; notes support text edit
- Delete works for both auto events and manual notes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Adds strikes (0-3) to Child model with migration
- New PATCH /api/children/{id}/strikes endpoint with WebSocket broadcast
- TV dashboard shows red ✕ marks next to child name when strikes > 0
- 3 Strikes card on Dashboard page (removed from Admin)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows "Good Morning! Ready to start school?" placeholder when a session
is active but no block has been selected, along with a live countdown
to the first scheduled block's start time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend queries and model relationship now order by time_start.
Frontend also sorts blocks client-side for reliability across all views.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scale timer ring up to 420px to fill the column, increase countdown
digits to 5.5rem and label to 1.4rem.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Activity items: 1.6rem → 2rem
- Schedule block title: 1.2rem, time: 1rem, more padding
- Scoped to TV sidebar so other views are unaffected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Subject options:
- New subject_options table (auto-created on startup)
- SubjectOut now includes options list; all eager-loading chains updated
- Admin: Options panel per subject with add, inline edit, and delete
- WS broadcast and dashboard API include options in block subject data
TV dashboard:
- Three equal columns: Timer | Activities | Schedule
- Activities column shows current subject's options in large readable text
- Activities area has subject-colored border and tinted background
- Subject name and label displayed correctly using embedded subject data
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clear blocks, completedBlockIds, and day times alongside session when
the is_active: false WS event is received.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The WS broadcast payload was missing day_start_time, day_end_time, and
duration_minutes, so applySnapshot nulled them out on session_update.
Now _broadcast_session fetches the template and includes all fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Display effective duration (time window or custom override) on every
block list — Admin, Dashboard, and TV sidebar. Custom duration_minutes
values are highlighted in indigo to distinguish them from the default
time-window calculation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Separates "recommended time" (time_start/time_end) from actual timer
duration. If duration_minutes is set, the timer counts down from that
value; otherwise falls back to the time_start–time_end window.
- ScheduleBlock model: add nullable duration_minutes INT column
- Startup migration: idempotent ADD COLUMN for existing DBs
- Schemas: duration_minutes in create, update, and out
- TimerDisplay: prefer duration_minutes * 60 over time diff when set
- Admin block forms: Duration (min) input on add and edit forms
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backend: PATCH /api/schedules/{template_id}/blocks/{block_id} endpoint
- Frontend: Edit button on each block row expands an inline form
pre-filled with current subject, times, and label; saves via PATCH
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces block-count progress with a wall-clock progress bar driven by
configurable day start/end hours on each schedule template.
- ScheduleTemplate: add day_start_time / day_end_time (TIME, nullable)
- Startup migration: idempotent ALTER TABLE for existing DBs
- Dashboard snapshot: includes day_start_time / day_end_time from template
- Admin → Schedules: time pickers in block editor to set day hours
- Dashboard view: time-based progress bar with start/current/end labels
- TV view: full-width day progress strip between header and main content
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move schedule template management into AdminView under a new Schedules section
- Remove ScheduleView.vue and its route, drop Schedules link from NavBar
- Delete docker-compose.override.yml (dev override no longer needed)
- Fix CORS_ORIGINS default to port 8057 in docker-compose.yml
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backend computes block_elapsed_seconds server-side from timer_events
- Store tracks blockStartedAt (ms) + blockElapsedOffset (seconds) instead
of a client-side counter; updated correctly on start/pause/resume/end
- TimerDisplay derives elapsed from store props so both views always agree
- Add compact timer display to dashboard session card
- Add isPaused/pause-resume logic to dashboard Pause/Resume buttons
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use connected ref directly from useWebSocket instead of copying the value,
so the Live/Reconnecting indicator updates reactively. Restore ref import
that was incorrectly removed (still needed for the clock).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Broadcast is_active in WS timer payload so the frontend can immediately
clear the session when the backend marks it complete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add passive_deletes=True to daily_sessions and activity_logs relationships
so the ORM defers to MySQL's ON DELETE CASCADE instead of trying to SET NULL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>