- 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>
12 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, 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.
- Schedule Builder — Create named schedule templates with time blocks assigned to subjects. Set optional school day start/end hours for a day-progress bar. 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, and Reset to clear the elapsed time and restart the timer from zero. Elapsed time per block is remembered across switches, so returning to a block picks up where it left off.
- 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.
- Activity Log — Automatically records every timer event (day started, block start/pause/resume/complete/skip/reset) and every strike change as a timestamped timeline. 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 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) |
| 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
│ ├── 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
│ │ ├── child.py
│ │ ├── subject.py # Subject + SubjectOption
│ │ ├── schedule.py # ScheduleTemplate + ScheduleBlock
│ │ ├── session.py # DailySession + TimerEvent
│ │ ├── activity.py # ActivityLog (manual notes)
│ │ ├── morning_routine.py# MorningRoutineItem
│ │ └── strike.py # StrikeEvent (strike history)
│ ├── schemas/ # Pydantic request/response schemas
│ ├── routers/ # API route handlers
│ │ ├── auth.py
│ │ ├── children.py
│ │ ├── subjects.py
│ │ ├── schedules.py
│ │ ├── sessions.py # Timer actions, block selection, reset
│ │ ├── logs.py # Timeline + strike events
│ │ ├── morning_routine.py
│ │ ├── dashboard.py # Public snapshot endpoint (TV)
│ │ └── users.py
│ ├── utils/
│ │ └── timer.py # Shared elapsed-time computation (reset-aware)
│ └── 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, LogView, AdminView
└── components/ # TimerDisplay, ScheduleBlock, NavBar, etc.
Getting Started
Prerequisites
- Docker and Docker Compose installed
- Port
8054available on the host (or change it indocker-compose.yml)
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. Add activity options to each subject — they appear on the TV dashboard during that block.
- Admin → Add Morning Routine items — these show on the TV during the greeting before the first block starts.
- Admin → Scroll to Settings and select your local timezone
- Admin → Scroll to Schedules → Create a schedule template, set school day hours, 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, 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, schedule templates, and account settings |
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 then provide explicit control:
| 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 and restart timer immediately |
| End Day | Session active | Mark the session complete |
TV Dashboard (no login)
| URL | Description |
|---|---|
/tv/:childId |
Full-screen display — greeting + morning routine, current block timer, activity options, day progress, schedule sidebar |
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
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, 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 |
select |
Block selected (not started) | block_id, current_block_id, block_elapsed_seconds, prev_block_id, prev_block_elapsed_seconds |
reset |
Block timer reset to zero | block_id, current_block_id, block_elapsed_seconds (always 0) |
complete |
Session ended | is_active: false |
strikes_update |
Strike issued/cleared | strikes |
block_elapsed_seconds on start and select events carries the authoritative accumulated elapsed time for that block (all previous intervals, respecting any prior resets), so every client — including the TV — can restore the correct timer offset without a local cache.
prev_block_id and prev_block_elapsed_seconds on start and select events carry the saved elapsed for the block being left, so the TV sidebar immediately shows the correct remaining time for that block.
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