← All work
Side project  Write-up

DeskPilot

A dashboard built for a screen that sits on your desk. It pulls tasks, calendar events, email, weather, and news into one view, works out what needs attention first, and updates live without the page ever polling.

11
dashboard widgets
8
background jobs per user
4
outside APIs: Gmail, Calendar, weather, news
2 min
how often alerts are recomputed
Role · Solo Stack · React · Vite · TypeScript · Tailwind · Zustand · FastAPI · APScheduler · Firebase
Public repo
Jun 14, 2026
Commits
1cleaned-up export
Code
2.9k lines1.9k TS/TSX, 960 Python
Files
58
01 · Timeline

One export commit

I published DeskPilot as a single cleaned-up commit, so GitHub only shows the day it went public. The structure of the repo is a better guide to the scope: a React front end, a FastAPI back end with a scheduler, and Firebase in between.

  1. Jun 14, 2026
    Published

    Front end with dashboard, focus, settings, and login views; FastAPI back end with Google OAuth, scheduled refresh jobs, and alert and briefing services; environment-driven config with no credentials in the repo.

    GitHub58 files · +6,973 lines2d024d8 ↗
02 · How it works

The back end does the work, Firebase carries it to the screen

A FastAPI service runs APScheduler jobs for every registered user. Each job refreshes one kind of data (weather, news, Gmail, calendar) on its own interval and writes the result into that user's node in Firebase Realtime Database. Derived jobs run on top: task priorities every 10 minutes, metrics every 5, alerts every 2, and a daily briefing.

The React app never asks the server for updates. It subscribes to the user's Firebase node, so when a job writes, the widget changes on its own. That's what makes it work on a wall or desk display that stays open all day.

Gmail · CalendarGoogle OAuth
Weather · NewsOpenWeatherMap · NewsAPI
→
FastAPIAPScheduler jobs
priorities · alerts · briefing
writes
→
Firebase RTDBone node per user
live
→
React dashboard11 widgets

The front end only subscribes. All fetching and scoring happens on the server.

03 · Deciding what's urgent

Deadline alerts in plain time tiers

Alerts come from tasks, calendar events, and email. Deadline alerts use plain time tiers, so the reason something is red is obvious from across the room.

backend/app/services/alerts_service.py
diff_hours = (deadline - now) / 3600
if diff_hours < 0:
    severity = "critical"
    msg = f"OVERDUE: \"{t['title']}\" was due {abs(int(diff_hours))}h ago"
elif diff_hours <= 2:
    severity = "critical"
    msg = f"Due in {int(diff_hours * 60)} min: \"{t['title']}\""
elif diff_hours <= 6:
    severity = "high"
    msg = f"Due in {int(diff_hours)}h: \"{t['title']}\""
elif diff_hours <= 24:
    severity = "medium"
    msg = f"Due today: \"{t['title']}\""
else:
    continue

From alerts_service.py.

Tasks and emails also carry a priority score and a label (urgent, high, and so on). The email widget sorts by score and shows an urgent count at the top, and a Top Priorities widget merges the highest-scoring tasks and emails into one list.

04 · What's on the screen

Eleven widgets and a focus mode

At a glance

Clock, weather, today's calendar, and a short daily briefing.

What needs doing

Tasks, Top Priorities, alerts, and an email list sorted by urgency.

Context

News headlines and a metrics panel.

Focus

A separate focus view for heads-down work.

Read the code.

Setup needs your own Firebase project and API keys.

More work