A single-page, open-source household chore dashboard designed for always-on tablets and touch screens. Installable as a PWA for true offline kiosk mode.
- Touch-first kiosk UI β landscape, no-scroll, fullscreen-friendly with 44px minimum tap targets
- Dual-view grid β switch between π Room View (one row per room) and π Task View (one row per task type, aggregated across rooms)
- Drill-down navigation β tap aggregate cells or row headers to see individual tasks within a room, or individual rooms for a task type; navigate back with the β breadcrumb bar
- Per-room task definitions β each room can have named tasks (e.g. "Vacuum", "Sweep/Mop"), each with its own independent clean cycle; rooms without tasks use simple single-tap toggle
- Task type aggregation β tasks with the same name across different rooms are automatically merged in Task View (e.g. all "Vacuum" tasks appear as one row showing completion across rooms)
- Configurable clean cycles β each task (or simple room) has a "stays clean for N days" setting (1β30)
- Color gradient status β green β neutral β red based on how overdue a task is, with future-day projections shown at reduced opacity
- Auto-sort by urgency β rows automatically reorder so the most overdue appear at the top, with smooth FLIP animation; sort order is frozen during drill-down to prevent disorienting jumps
- Smart single-room toggle β in Task View, tapping a day-cell for a task type that exists in only one room directly toggles it instead of drilling down
- Today column highlight β current day column emphasized with a blue inset border and auto-scrolled into view
- Date navigation β arrow icons on the first and last date column headers let you shift the visible date window forward or backward
- Footer summary row β sticky bottom row showing maintain/proactive/newly-due breakdown per day, scoped to the current view or drill-down
- Cell tooltip β hover or long-press a cell to see detailed status (days since cleaned, days until due, who cleaned, projected state for future dates)
- Sparkline history β compact canvas chart in the top bar showing clean vs overdue trends over a configurable period, with Sunday tick marks
- Full-width infographic β opens as a modal dialog (up to 1200px wide) with hero stats, heatmaps, leaderboards, and room health cards
- Hero stats β four cards at the top showing tasks done today, overall clean percentage, current activity streak, and 7-day MVP
- Activity heatmap β 30-day grid with per-user rows and a total row; cells colored by intensity; month and ISO week header rows with alternating backgrounds to show period boundaries
- Today section β two-column layout with a prioritized hit list (top 5 most overdue tasks) on the left and done-today grouped by user plus room health mini-cards on the right
- Weekly comparison β This Week and Last Week side-by-side, each with due/done/net stats, user leaderboard with progress bars, medal rankings, maintain/progress breakdown, and badges (Most Tasks, Deep Clean, Proactive)
- Monthly comparison β This Month and Last Month side-by-side with the same leaderboard and badge format
- Collapsible sections β each period card can be collapsed/expanded with a click
- Responsive β collapses to single-column on tablet (768px), compact mode on phone (480px) with heatmap numbers hidden
- User selection β tap a name chip in the top bar before checking off a chore; cells show computed shortest-unique prefixes to distinguish users
- User management β add/remove users from the settings panel
- Dark & Light themes β toggle in settings; all colors adapt via CSS custom properties
- Import / Export β back up and restore all data as a JSON file
- Reset All β wipe all data and restore defaults from the settings panel
- PWA / Installable β manifest + service worker for "Add to Home Screen" and full offline support
- Offline-first β all data lives in
localStorageby default; nothing leaves your browser unless you opt in to sync - Optional multi-device sync β point any number of browsers at a lightweight Python sync server to share data across tablets/phones on the same network, with real-time WebSocket push updates
- Zero dependencies β single HTML file, no build step, no frameworks
- Self-hosted Poppins font β three weights (400/600/700) bundled as woff2 for offline use
- Auto-refresh β table and history chart re-render every 60 seconds to handle day rollover
Just open index.html in any modern browser, or serve it locally:
python3 -m http.server 8765
# open http://localhost:8765The sync server handles data synchronisation and serves the app itself, so clients only need a URL β no local files required.
# One-time setup (pick one)
pip install websockets # via pip
apt install python3-websockets # via apt (Debian/Raspberry Pi OS)
# Start the sync server (defaults: port 8780, WS on 8781)
python3 server/server.py
# β Static: http://0.0.0.0:8780/
# β REST: http://0.0.0.0:8780/data
# β WebSocket: ws://0.0.0.0:8781On each device, just visit http://<server-ip>:8780 (or open β Settings and enter the server URL to enable sync from a locally-opened file). A green dot in the top bar confirms a live connection. Data syncs instantly via WebSocket; if the connection drops, the app continues working offline from localStorage and re-syncs when the server is reachable again.
Server options:
| Flag | Default | Description |
|---|---|---|
--port |
8780 |
Port for REST API & static files (WebSocket listens on port+1) |
--data |
chore-data.json |
Path to the JSON data file |
--static |
(auto-detected) | Directory to serve static files from; set to '' to disable |
Examples:
python3 server/server.py --port 80 # serve on port 80 (requires root), WS on 81
python3 server/server.py --data /tmp/data.json # custom data file location
python3 server/server.py --static '' # sync-only, no static file servingArchitecture:
GET /β servesindex.htmland all static assets (HTML, JS, fonts, icons, manifest)GET /dataβ fetch the full data blob (REST)PUT /dataβ save the full data blob; server stamps a_versionfield (REST)ws://<host>:<port+1>β server broadcastsdata-changedto all connected clients on every PUT- Clients write to localStorage immediately (instant UI), then push to the server in the background
- Each device's selected user and theme are kept local (not synced)
A systemd service file is included at server/chore-tracker.service for auto-starting on boot (e.g. on a Raspberry Pi).
For kiosk mode:
- Chromium:
chromium --kiosk --app=http://<server-ip> - Firefox: press
F11for fullscreen
- Manage rooms β tap "β Rooms" to open the room manager; add, edit, or delete rooms
- Add tasks to a room β in the room editor, add named tasks (e.g. "Vacuum", "Mop") with individual clean cycles; autocomplete suggests task names used in other rooms and auto-fills the most common cycle length
- Add users β open β Settings, type a name and press Enter or "+"
- Select yourself β tap your name chip in the top bar (highlighted with a blue border)
- Switch views β use the π / π toggle to switch between Room View and Task View
- Mark chores done β tap a cell in the grid; for rooms with tasks, tap aggregate cells to drill down, then check off individual tasks
- Tap again to undo β toggle off a mistaken entry
- Drill down β tap a room name (Room View) or task type name (Task View) to see the breakdown; tap β Back to return
- Adjust settings β click β to change visible days, history range, color theme, or import/export data
- Review progress β tap π Review in the top bar to open the full-width dashboard with hero stats, activity heatmap, hit list, leaderboards, and room health cards
- Install as app β use your browser's "Add to Home Screen" or "Install App" option for a standalone kiosk experience
index.html β entire application (HTML + CSS + JS)
manifest.json β PWA web app manifest
sw.js β service worker for offline caching
fonts/ β self-hosted Poppins woff2 (400, 600, 700)
icons/ β PWA & favicon icons (16, 32, 192, 512, maskable-512)
server/ β optional sync server for multi-device use
server.py β REST + WebSocket + static file server (Python 3, one dependency)
chore-tracker.service β systemd unit file for auto-start on boot
LICENSE β MIT license
README.md β this file
All data is stored in localStorage under the key chore-tracker-data:
{
"rooms": [
{
"id": "...",
"name": "Kitchen",
"desc": "...",
"cleanDays": 1,
"tasks": [
{ "id": "...", "label": "Do Dishes", "cleanDays": 1 },
{ "id": "...", "label": "Sweep/Mop", "cleanDays": 30 }
]
}
],
"users": ["Alice", "Bob"],
"entries": {
"2026-02-17": {
"room-id": {
"cleaned": true,
"user": "Alice",
"tasks": {
"task-id": { "cleaned": true, "user": "Alice" }
}
}
}
},
"settings": { "daysShown": 14, "historyDays": 30, "theme": "dark" },
"selectedUser": "Alice"
}- Rooms without a
tasksarray (or with an empty one) use simple single-tap toggle and store only{ cleaned, user }in entries. - Rooms with tasks store per-task completion under
entries[date][roomId].tasks[taskId]. - Old entries (>120 days) are automatically garbage-collected on load.
Problem: Not every room needs cleaning on the same cadence or in the same context. A "Daily Tidy" schedule is different from a "Deep Clean" schedule, but they may overlap on the same physical rooms.
Approaches considered:
| Approach | Description | Pros | Cons |
|---|---|---|---|
| A β Tag/filter | Add tags to rooms (e.g. daily, deep) and filter the grid view |
Simple, no data model change | Tags are ambiguous; a room can appear in multiple views with conflicting state |
| B β Room groups | Nest rooms inside named groups; grid shows one group at a time | Clean visual separation | Still shares entries β marking "Kitchen" clean in Daily also marks it in Deep Clean |
| C β Separate schedules β | Top-level Schedule concept, each with its own independent rooms, entries, and settings |
Clean data model, no state conflicts, scales to any workflow | Slightly more complex UI (tab bar) |
Recommended: Approach C β Separate Schedules
- Add
DATA.schedules[]array, each schedule containing its ownrooms,entries, andsettings. - Add
DATA.activeScheduleindex to track the currently viewed schedule. - Render a tab bar (or swipeable tabs) above the grid to switch schedules.
- Existing single-schedule data migrates into
schedules[0]automatically. - Export/import covers all schedules in one JSON file.
Data model sketch:
{
"schedules": [
{
"id": "...",
"name": "Daily Tidy",
"rooms": [...],
"entries": {...},
"settings": { "daysShown": 14, "historyDays": 30 }
},
{
"id": "...",
"name": "Deep Clean",
"rooms": [...],
"entries": {...},
"settings": { "daysShown": 14, "historyDays": 30 }
}
],
"activeSchedule": 0,
"users": ["Alice", "Bob"],
"selectedUser": "Alice",
"settings": { "theme": "dark" }
}MIT β see LICENSE