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>
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, and upcoming schedule blocks. Updates live without page refresh.
- Schedule Builder — Create named schedule templates with time blocks assigned to subjects. Assign templates per-child or share across all children.
- Daily Sessions — Start a school day against a schedule template. Track which blocks are active, paused, or complete. Timer state (including paused) is preserved when navigating between pages. Switching to a different block auto-pauses the current one; returning to it resumes from where it left off.
- Activity Log — Automatically records every timer event (day started, block start/pause/resume/complete/skip) as a timeline, including which schedule template was used. Supports manual notes with subject, duration, and free text. Browse and filter history by child and date.
- Behavior Tracking (Strikes) — Issue up to 3 strikes per child from the Dashboard. Strike count is shown on the TV dashboard and resets automatically when a new school day begins.
- Timezone Support — Set your local timezone in Admin → Settings. All activity log timestamps display in your timezone, including the TV dashboard clock.
- 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).
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) |
| Migrations | Alembic |
| Auth | JWT — python-jose + passlib/bcrypt |
| Orchestration | Docker Compose |
Project Structure
homeschool/
├── docker-compose.yml # Production stack (3 services)
├── docker-compose.override.yml # Dev overrides (hot reload)
├── .env.example # Environment variable template
│
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── alembic/ # Database migrations
│ └── app/
│ ├── main.py # FastAPI app entry point
│ ├── config.py # Settings (reads from .env)
│ ├── database.py # Async SQLAlchemy engine
│ ├── dependencies.py # Auth dependencies (get_current_user)
│ ├── auth/jwt.py # Token creation, password hashing
│ ├── models/ # SQLAlchemy ORM models
│ ├── schemas/ # Pydantic request/response schemas
│ ├── routers/ # API route handlers
│ └── websocket/manager.py # WebSocket connection manager
│
└── frontend/
├── Dockerfile # Multi-stage: Node build → nginx serve
├── nginx.conf # Proxy /api/ and /ws/ to backend
└── src/
├── composables/
│ ├── useApi.js # Axios with auto token-refresh
│ └── useWebSocket.js # Auto-reconnecting WebSocket
├── stores/ # Pinia: auth, children, schedule
├── views/ # LoginView, TVView, DashboardView, etc.
└── components/ # TimerDisplay, ScheduleBlock, NavBar, etc.
Getting Started
Prerequisites
- Docker and Docker Compose installed
- Port
8054available on the host
1. Clone the repo
git clone https://git.chns.tech/CooperandGoodman/homeschool.git
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
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)
- Admin (
/admin) → Add each child, pick a color - Admin → Add subjects (Math, Reading, Science, etc.) with emoji icons and colors
- Admin → Scroll to Settings and select your local timezone — this ensures activity log times and the TV clock display correctly
- Schedules (
/schedules) → Create a schedule template, add time blocks assigned to subjects - Dashboard (
/dashboard) → Click "Start Day", choose a template - TV → Open
http://your-lan-ip:8054/tv/1on the living room TV (replace1with the child's ID)
Usage
Parent Views (require login)
| URL | Description |
|---|---|
/dashboard |
Overview, start/stop sessions, timer controls, issue behavior strikes |
/schedules |
Create and edit schedule templates and time blocks |
/logs |
Browse timer event history and manual activity notes; filter by child and date |
/admin |
Manage children, subjects, schedule templates, and account settings (timezone) |
TV Dashboard (no login)
| URL | Description |
|---|---|
/tv/:childId |
Full-screen display — current block, countdown timer, day progress |
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 UI —
http://localhost:8054/api/docs - ReDoc —
http://localhost:8054/api/redoc
Development Mode
The docker-compose.override.yml file enables hot reload automatically when running locally:
- Backend — uvicorn
--reloadwatchesbackend/app/for changes - Frontend — Vite HMR on port
5173(mapped to8054in dev mode)
docker compose up --build # override is applied automatically in dev
To run production mode explicitly (no hot reload):
docker compose -f docker-compose.yml up --build
Database Migrations
Alembic is configured for async SQLAlchemy migrations.
# Generate a new migration after changing models
docker compose exec backend alembic revision --autogenerate -m "description"
# Apply pending migrations
docker compose exec backend alembic upgrade head
# Roll back one migration
docker compose exec backend alembic downgrade -1
Note: On first startup the app auto-creates all tables via SQLAlchemy's
create_all. Alembic is used for schema changes after the initial setup.
WebSocket Events
The TV dashboard connects to ws://host/ws/{child_id} and receives JSON events:
| Event | Triggered by | Payload |
|---|---|---|
session_update |
Session start/end | Full session snapshot |
start |
Timer started | session_id, block_id, current_block_id |
pause |
Timer paused | session_id, block_id |
resume |
Timer resumed | session_id, block_id |
complete |
Block completed | session_id, block_id |
skip |
Block skipped | session_id, block_id |
strikes_update |
Strike issued/cleared | child_id, strikes |
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) |
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