Files
homeschool/README.md
derekc ff9a863393 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>
2026-03-06 00:08:15 -08:00

22 KiB

Homeschool Dashboard

A self-hosted web app for managing homeschool schedules, tracking daily learning sessions, and logging activities. Features a full-screen TV dashboard with live timers and real-time updates via WebSockets.


Features

  • TV Dashboard — Full-screen display for the living room TV. Shows the current subject, countdown timer, day progress bar, activity options, and the schedule block list. Updates live without page refresh via WebSocket.
  • Morning Routine — Define a list of morning routine items in Admin. They appear in the TV dashboard Activities panel during the "Good Morning" greeting before the first block starts, then switch to subject-specific activities once a block begins.
  • Break Time — Each schedule block can optionally include a break at the end. Enable the checkbox and set a duration (in minutes) when building a block in Admin. Once the block's main timer is done, a Break Time section appears on the Dashboard with its own Start / Pause / Resume / Reset controls — the break does not start automatically. While break is active the TV left column switches to an amber break badge and countdown timer, and the center column shows the configurable Break Activities list instead of subject options. Break timer state is fully restored when navigating away and returning to the dashboard.
  • Break Activities — A global list of break-time activities (e.g. "Get a snack", "Go outside") managed in Admin → Break Activities, using the same add/edit/delete interface as Morning Routine. These items are shown on the TV during any active break.
  • Day Progress Bar — Both the TV dashboard and the parent dashboard display a progress bar showing how far through the day the child is. Progress is calculated from total scheduled block time vs. remaining block time — not wall-clock time — so it advances only as blocks are actively worked. On the TV the bar is labeled Start and Finish. On the parent dashboard the left label shows the scheduled start time of the first block and the right label shows a live-updating estimated finish time computed as the current time plus all remaining block time and break time for incomplete blocks.
  • Schedule Builder — Create named schedule templates with time blocks assigned to subjects. Each block supports an optional custom duration override, label, and break time setting. Managed inside the Admin page.
  • Daily Sessions — Start a school day against a schedule template. Click any block in the list to select it as the current block. Use the Start button to begin timing, Pause to stop, Resume to continue from where you left off, Done to mark it as fully complete, and Reset to clear the elapsed time back to zero (timer stays paused). Elapsed time per block is remembered across switches, so returning to a block picks up where it left off.
  • Done Button — Marks the current block as fully elapsed (timer snaps to 00:00 / "Done!"), adds a checkmark in the schedule list, and logs "Marked Done by User" in the activity log. Pressing Reset after Done clears the completed state entirely — removing the checkmark and the Done label — treating the block as if it were never started.
  • Block Timer Remaining — Each block in the schedule list shows time remaining (allocated duration minus elapsed), counting down live on both the parent dashboard and the TV sidebar. Shows "< 1 min" when under a minute, and "Done!" when the full duration is elapsed.
  • Tablet-Friendly Controls — The session control buttons (Start/Pause/Resume, Reset, Done, End Day) stretch full-width across the session card and are taller for easy tapping on a tablet.
  • Activity Log — Automatically records every timer event (day started, block start/pause/resume/complete/reset, break start/pause/resume/reset) as a timestamped timeline. Block selection events are intentionally not logged. The "Done" button logs as "Marked Done by User — Block Name". All event types display with a human-readable label and icon. Includes which schedule template was used. Supports manual notes with free text. Browse and filter history by child and date.
  • Behavior Tracking (Strikes) — Issue up to 3 strikes per child from the Dashboard. Strike additions and removals are logged in the activity log with a timestamp. Strike count is shown on the TV dashboard. Strikes reset automatically at midnight in the user's configured timezone — the reset happens lazily on the first page load after midnight, so no scheduled job is required.
  • Timezone Support — Set your local timezone in Admin → Settings. All activity log timestamps display in your timezone, including the TV dashboard clock. Midnight strike resets use this timezone.
  • Password Change — Users can change their own account password from Admin → Settings → Reset Password. The form requires the current password before accepting a new one.
  • Multi-Child Support — Manage multiple students under one parent account, each with their own color, schedule, and history.
  • JWT Authentication — Secure parent login with access tokens and httpOnly refresh cookies. TV dashboard is public (no login required). Disabled accounts receive a clear error message explaining the account is disabled rather than a generic "invalid credentials" response.
  • Super Admin Panel — A separate admin interface (at /super-admin) for site-wide management. Log in with a dedicated admin username and password (set in .env). Provides full control over all registered parent accounts:
    • Impersonate — Enter any user's session to view and manage their data. An impersonation banner is shown at the top of every page with a one-click "Exit to Admin Panel" button.
    • Reset Password — Set a new password for any user without needing the current password.
    • Disable / Enable — Disable a user's login access. Disabled users cannot log in and see a specific error message. Re-enable at any time.
    • Delete — Permanently delete a user and all associated data (children, sessions, schedules, subjects, activity logs, etc.) with a confirmation dialog. This action cannot be undone.
    • Last Active — Shows the date each user last logged in or refreshed their session, displayed in that user's configured timezone. Shows "Never logged in" for accounts that have never authenticated.
  • Meeting Subject — A system subject called "Meeting" (calendar icon) is automatically created for every user and cannot be deleted or renamed. Add it to any schedule block like a normal subject and assign activity options (e.g. agenda items) that will display on the TV during the meeting.
  • Meeting Notifications — When a schedule block assigned to the Meeting subject is approaching, the app automatically alerts everyone:
    • 5-minute warning — An amber corner toast appears on both the parent Dashboard and the TV with the meeting name and a live countdown. Tap X to dismiss.
    • 1-minute re-notify — If the 5-minute toast was dismissed, it reappears at the 1-minute mark.
    • At start time — A full-screen overlay fires on the TV with the meeting name and a 30-second auto-dismiss countdown (tap anywhere to dismiss early). Simultaneously, the schedule switches to the Meeting block and its timer starts automatically.
    • Chime sounds — A rising three-note chime plays on warnings; a falling chime plays at meeting start. Generated via the Web Audio API — no audio files required.

Tech Stack

Layer Technology
Frontend Vue 3 + Vite + Pinia + Vue Router
Frontend server nginx (Docker)
Backend API FastAPI (Python 3.12)
Real-time WebSockets via FastAPI
Database MySQL 8
ORM SQLAlchemy 2.0 (async)
Auth JWT — python-jose + passlib/bcrypt
Orchestration Docker Compose

Project Structure

homeschool/
├── docker-compose.yml            # Stack definition (3 services: db, backend, frontend)
├── .env.example                  # Environment variable template
│
├── backend/
│   ├── Dockerfile
│   ├── requirements.txt
│   └── app/
│       ├── main.py               # FastAPI app entry point + table auto-creation + migrations
│       ├── config.py             # Settings (reads from .env)
│       ├── database.py           # Async SQLAlchemy engine
│       ├── dependencies.py       # Auth dependencies (get_current_user, get_admin_user)
│       ├── auth/jwt.py           # Token creation, password hashing
│       ├── models/               # SQLAlchemy ORM models
│       │   ├── child.py          # Child (incl. strikes, strikes_last_reset)
│       │   ├── subject.py        # Subject + SubjectOption
│       │   ├── schedule.py       # ScheduleTemplate + ScheduleBlock (incl. break fields)
│       │   ├── session.py        # DailySession + TimerEvent
│       │   ├── activity.py       # ActivityLog (manual notes)
│       │   ├── morning_routine.py# MorningRoutineItem
│       │   ├── break_activity.py # BreakActivityItem
│       │   ├── strike.py         # StrikeEvent (strike history)
│       │   └── user.py           # User (incl. timezone, last_active_at)
│       ├── schemas/              # Pydantic request/response schemas
│       ├── routers/              # API route handlers
│       │   ├── auth.py           # Login, register, refresh, logout, change-password
│       │   ├── children.py       # Children CRUD + strikes + midnight reset
│       │   ├── subjects.py
│       │   ├── schedules.py
│       │   ├── sessions.py       # Timer actions + break timer events
│       │   ├── logs.py           # Timeline + strike events
│       │   ├── morning_routine.py
│       │   ├── break_activity.py # Break activities CRUD
│       │   ├── dashboard.py      # Public snapshot endpoint (TV + dashboard reload)
│       │   ├── admin.py          # Super admin: login, user list, impersonate, reset-password,
│       │   │                     #   toggle-active, delete-user
│       │   └── users.py
│       ├── utils/
│       │   └── timer.py          # Elapsed-time computation for block and break timers
│       └── websocket/manager.py  # WebSocket connection manager
│
└── frontend/
    ├── Dockerfile                # Multi-stage: Node build → nginx serve
    ├── nginx.conf                # Proxy /api/ and /ws/ to backend
    ├── index.html                # Favicon set to house emoji via inline SVG
    └── src/
        ├── composables/
        │   ├── useApi.js         # Axios with auto token-refresh
        │   ├── useWebSocket.js   # Auto-reconnecting WebSocket
        │   └── useMeetingAlerts.js # Meeting countdown, corner toasts, TV overlay, chimes
        ├── stores/               # Pinia: auth, children, schedule, superAdmin
        ├── views/                # LoginView, TVView, DashboardView, LogView, AdminView,
        │                         #   SuperAdminLoginView, SuperAdminView
        └── components/           # TimerDisplay, ScheduleBlock, NavBar, etc.

Getting Started

Prerequisites

  • Docker and Docker Compose installed
  • Port 8054 available on the host (or change it in docker-compose.yml)

1. Clone the repo

git clone <your-repo-url>
cd homeschool

2. Create your .env file

cp .env.example .env

Open .env and fill in the values:

MYSQL_ROOT_PASSWORD=your_secure_root_password
MYSQL_DATABASE=homeschool
MYSQL_USER=homeschool
MYSQL_PASSWORD=your_secure_db_password

# Generate with: openssl rand -hex 32
SECRET_KEY=your_generated_secret_key

ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_DAYS=30

# Your host IP or domain (no trailing slash)
CORS_ORIGINS=http://localhost:8054

# Super admin credentials (for /super-admin)
ADMIN_USERNAME=admin
ADMIN_PASSWORD=change_me_admin_password

3. Build and start

docker compose up --build

The first build takes a few minutes (npm install + pip install). On subsequent starts it's fast.

4. Register your parent account

Open http://localhost:8054/login and register. This creates your admin account.

5. Set up your data (in order)

  1. Admin (/admin) → Add each child, pick a color
  2. Admin → Add subjects (Math, Reading, Science, etc.) with emoji icons and colors. Add activity options to each subject — they appear on the TV dashboard during that block. The Meeting subject is created automatically and cannot be deleted or renamed, but you can add activity options (agenda items) to it.
  3. Admin → Add Morning Routine items — these show on the TV during the greeting before the first block starts.
  4. Admin → Add Break Activities items — these show on the TV center panel whenever a break is active.
  5. Admin → Scroll to Settings and select your local timezone. You can also change your account password here.
  6. Admin → Scroll to Schedules → Create a schedule template, add time blocks assigned to subjects. For any block that should include a break, check Break and enter the break duration in minutes.
  7. Dashboard (/dashboard) → Click "Start Day", choose a template
  8. TV → Open http://your-lan-ip:8054/tv/1 on the living room TV (replace 1 with the child's ID)

Usage

Parent Views (require login)

URL Description
/dashboard Overview, start/stop sessions, select and time blocks, issue behavior strikes
/logs Browse timer and strike event history and manual notes; filter by child and date
/admin Manage children, subjects (with activity options), morning routine, break activities, schedule templates, and account settings (timezone, password)

Super Admin Views

URL Description
/super-admin/login Log in with the ADMIN_USERNAME / ADMIN_PASSWORD from .env
/super-admin Manage all registered parent accounts

Super admin actions per user: Enter as User (impersonate), Reset Password, Disable / Enable login access, Delete account and all data. Each user card also shows the account status badge, join date, and last active date (in the user's own timezone).

While impersonating, a yellow banner appears at the top of every page showing who you're viewing as, with an Exit to Admin Panel button to return.

Dashboard Controls

While a session is active, clicking a block in the schedule list selects it as the current block without starting the timer. The action buttons provide explicit control and span the full width of the card for easy tapping on a tablet:

Main block timer:

Button Condition Action
Start Block selected, never timed Begin counting from zero
Resume Block was previously paused Continue from saved elapsed time
Pause Timer is running Stop counting, save elapsed time
Reset Any current block Clear elapsed to zero; timer stays paused
Done Any current block Snap timer to full duration (00:00 / "Done!"), mark block complete; logs "Marked Done by User"
End Day Session active Mark the session complete

Pressing Reset after Done fully un-marks the block — removes the checkmark, clears the completed state in the database, and treats the block as if it were never started.

Break timer (shown when the current block has Break Time enabled):

Button Condition Action
Start Break Break not yet started Begin break countdown from zero
Pause Break timer running Stop break countdown
Resume Break timer paused Continue break countdown
Reset Break in progress Clear break elapsed to zero and restart immediately

TV Dashboard (no login)

URL Description
/tv/:childId Full-screen display — greeting + morning routine, current block timer with subject activities, break timer with break activities, day progress bar, schedule sidebar, meeting warning toasts, meeting start overlay

Point a browser on the living room TV at http://your-lan-ip:8054/tv/1. The page connects via WebSocket and updates automatically when a parent starts/stops/advances the timer from the Dashboard.

API Documentation

FastAPI auto-generates interactive API docs:

  • Swagger UIhttp://localhost:8054/api/docs
  • ReDochttp://localhost:8054/api/redoc

Schema Migrations

The app automatically creates all database tables on startup via SQLAlchemy's create_all. Additive column changes are applied with idempotent ALTER TABLE statements in the startup lifespan, so upgrading to a new version is safe — just rebuild and restart:

docker compose build
docker compose up -d

No separate migration tool or manual steps are required.


WebSocket Events

The TV dashboard connects to ws://host/ws/{child_id} and receives JSON events:

Event Triggered by Key payload fields
session_update Session start Full session snapshot including blocks, morning routine, break activities, and day times
start Block timer started block_id, current_block_id, block_elapsed_seconds, prev_block_id, prev_block_elapsed_seconds
pause Block timer paused block_id, current_block_id
resume Block timer resumed block_id, current_block_id
reset Block timer reset to zero block_id, current_block_id, block_elapsed_seconds (always 0), uncomplete_block_id
complete Block marked done / session ended block_id (if block done), is_active: false (if session ended)
break_start Break timer started block_id, current_block_id, break_elapsed_seconds, block_elapsed_seconds
break_pause Break timer paused block_id, current_block_id
break_resume Break timer resumed block_id, current_block_id
break_reset Break timer reset to zero block_id, current_block_id, break_elapsed_seconds (always 0)
strikes_update Strike issued/cleared/midnight reset strikes

Notes:

  • block_elapsed_seconds on start carries the authoritative accumulated elapsed time so every client can restore the correct timer offset without a local cache.
  • prev_block_id and prev_block_elapsed_seconds on start carry the saved elapsed for the block being left, so the TV sidebar immediately shows the correct remaining time for that block.
  • uncomplete_block_id on reset tells clients to remove that block from the completed list (used when Reset is pressed after Done).
  • select events are broadcast via WebSocket but are not persisted to the database or shown in the activity log.
  • Implicit pause events (written when switching blocks or starting a break) are only recorded if the block's timer was actually running — no duplicate pauses are written if the block was already paused or never started.
  • Break timer events (break_*) do not affect block selection or elapsed time for the main block timer.

Activity Log Event Types

Event Display label When recorded
session_start Day started New session begins
start Started Block timer starts
pause Paused Block timer stops (manual or implicit on block switch)
resume Resumed Block timer continues after a pause
reset Reset Block timer cleared to zero
complete (no block) Day ended Session marked complete
complete (with block) Marked Done by User Done button pressed on a block
break_start Break started Break timer starts
break_pause Break paused Break timer stops
break_resume Break resumed Break timer continues
break_reset Break reset Break timer cleared

select events are not recorded in the activity log.


Environment Variables Reference

Variable Required Description
MYSQL_ROOT_PASSWORD Yes MySQL root password
MYSQL_DATABASE Yes Database name (default: homeschool)
MYSQL_USER Yes App database user
MYSQL_PASSWORD Yes App database password
SECRET_KEY Yes JWT signing key — generate with openssl rand -hex 32
ALGORITHM No JWT algorithm (default: HS256)
ACCESS_TOKEN_EXPIRE_MINUTES No Access token lifetime (default: 30)
REFRESH_TOKEN_EXPIRE_DAYS No Refresh token lifetime (default: 30)
CORS_ORIGINS No Comma-separated allowed origins (default: http://localhost:8054)
ADMIN_USERNAME No Super admin login username (default: admin)
ADMIN_PASSWORD No Super admin login password (default: change_me_admin_password)

Note: ADMIN_USERNAME and ADMIN_PASSWORD must be set in .env and listed in the backend service's environment block in docker-compose.yml. Changing them in .env alone is not sufficient — the backend container reads them as environment variables, not from the file directly.


Stopping and Restarting

# Stop containers (data preserved in Docker volume)
docker compose down

# Stop and wipe the database volume (full reset)
docker compose down -v

# Restart without rebuilding
docker compose up