Skip to main content

Command Palette

Search for a command to run...

I Built a Free macOS Task Manager in a Weekend Because I Was Tired of Paying for Todo Apps

Updated
7 min read

A full Google Calendar sync, native macOS app, built with Tauri + Rust + React — zero subscription fees, forever.


The spark hit me when I opened Todoist and saw the paywall for Google Calendar sync. A $48/year subscription. For syncing a calendar. Something that should be a basic feature.

I'm a CEO. I live in task managers. I've tried Todoist, Things 3, TickTick, Notion, Linear. They're all great — and they all want $50-100/year for features that should be free.

So I decided to build my own.

The Stack: Why Tauri?

I chose Tauri over Electron because I didn't want a 200MB app that eats RAM. Tauri apps are native — they use the OS's webview and a Rust backend. The Daily Planner app is under 10MB.

Stack:

  • Tauri 2.0 — native macOS wrapper

  • Rust — backend logic, database, OAuth

  • React 18 + TypeScript — frontend UI

  • SQLite via SQLx — local-first data storage, no server required

  • Tailwind CSS — styling

Why this stack? Because I wanted something that:

  1. Doesn't ship 200MB of Chromium

  2. Feels native to macOS (dock icon, menu bar, native notifications)

  3. Stores data locally (no cloud vendor lock-in)

  4. Is fast and efficient

  5. Can be built by one person in a weekend

Tauri + Rust hit all five.

The Architecture: Local-First

Most task apps store your data on their servers. Daily Planner stores everything locally in SQLite. Google Calendar sync is optional and additive — your data is yours.

The SQLite database lives at:

~/Library/Application Support/dev.inguva.daily-planner/daily-planner.db

You can back it up, copy it between machines, inspect it with any SQLite client. It's your data.

Why local-first?

Cloud sync is convenient, but it comes with vendor risk. If a SaaS company:

  • Gets acquired and shuts down

  • Changes pricing models

  • Has a data breach

  • Decides to pivot away from your use case

...your data is at risk or gone.

With local-first, I own my task history. If Daily Planner disappears tomorrow, my 5 years of task data is still on my machine.

The Google Calendar Sync: Secure OAuth Without a Backend

This was the fun part. I implemented a proper OAuth 2.0 PKCE flow — no server needed.

How it works:

  1. User clicks "Connect Google Calendar"

  2. App opens a local HTTP server on a random port (e.g., :59265)

  3. Browser opens to Google's OAuth consent page

  4. User signs in and authorizes access to calendar

  5. Google redirects to http://127.0.0.1:59265/callback?code=xyz

  6. App captures the code and exchanges it for access tokens

  7. Tokens stored locally, sync runs in background

  8. Browser tab closes automatically

PKCE (Proof Key for Code Exchange) is the secure standard for desktop apps. It's what mobile apps use. It doesn't require a backend server or any secrets transmitted over the internet.

No server. No database. No infrastructure costs. Completely free.

The Code

pub async fn start_flow(&self) -> Result<(String, String, u16), String> {
    let code_verifier = Self::generate_code_verifier();
    let code_challenge = Self::generate_code_challenge(&code_verifier);

    // Start local HTTP server
    let listener = TcpListener::bind("127.0.0.1:0").map_err(|e| e.to_string())?;
    let port = listener.local_addr().map_err(|e| e.to_string())?.port();

    // Build OAuth URL with PKCE challenge
    let redirect_uri = format!("http://127.0.0.1:{}/callback", port);
    let oauth_url = format!(
        "{}?client_id={}&redirect_uri={}&response_type=code&code_challenge={}&code_challenge_method=S256&access_type=offline&prompt=consent",
        GOOGLE_OAUTH_URL,
        urlencoding::encode(&self.client_id),
        urlencoding::encode(&redirect_uri),
        code_challenge
    );

    // Open browser
    open::that(&oauth_url)?;

    // Wait for callback
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        if let Ok((stream, _)) = listener.accept() {
            let _ = handle_callback(stream, &tx);
        }
    });

    let auth_code = rx.recv_timeout(std::time::Duration::from_secs(300))?;
    Ok((auth_code, code_verifier, port))
}

The entire flow is client-side. No backend. No webhook. Just a local server that lives for 5 seconds.

Priority → Calendar Color Mapping

One of my favorite features: task priority maps to Google Calendar event color.

  • P1 Urgent → Red (Tomato)

  • P2 High → Yellow (Banana)

  • P3 Medium → Green (Sage)

  • P4 Low → Purple (Lavender)

Your calendar becomes a visual priority map. Glance at your calendar and you immediately see what's urgent.

fn priority_to_calendar_color(priority: i32) -> &'static str {
    match priority {
        1 => "Tomato",    // Red
        2 => "Banana",    // Yellow
        3 => "Sage",      // Green
        _ => "Lavender",  // Purple
    }
}

What It Has (v0.1.0)

✅ Full task CRUD with priorities ✅ Due dates + times ✅ Recurring tasks (daily/weekly/monthly) ✅ Google Calendar two-way sync ✅ Tray icon for quick task entry ✅ Light/dark theme ✅ Keyboard shortcuts (⌘N for new task, ⌘K for search) ✅ Markdown support in task descriptions

What's Next

Phase 1 — Foundation Hardening (Week 2)

  • Token refresh (OAuth expires after 1 hour)

  • Pull from GCal (currently push-only)

  • Auto-sync every 15 minutes

  • Native macOS notifications

  • YEARLY recurrence support

  • Fix timezone handling

Phase 2 — Power User Features (Month 2)

  • Subtasks and projects

  • Natural language input ("Buy milk tomorrow at 5pm")

  • Drag & drop reordering

  • Keyboard-first navigation

  • Global hotkey for quick capture (⌘⇧Space)

Phase 3 — Smart Features (Month 3)

  • Focus mode / Pomodoro timer

  • Time tracking per task

  • AI-powered task prioritization

  • Weekly review summary

  • Smart scheduling suggestions

Phase 4 — Ecosystem (Month 4+)

  • Apple Reminders sync

  • Notion integration

  • Slack integration

  • GitHub Issues sync

  • iOS companion app

The Challenges

Challenge #1: Rust Learning Curve

I hadn't written Rust before this. The first 2 hours were painful. But the compiler errors are incredibly helpful. "You can't move this value because it doesn't implement Copy" is actually good feedback.

Rust forces you to think about ownership and memory. Once it clicks, you realize how many bugs this prevents.

Challenge #2: Tauri Build System

Bundling a native macOS app requires:

  • Signing with a developer certificate

  • Creating icons in 8 different sizes

  • Configuring the tauri.conf.json manifest

  • Learning how Tauri's preload scripts work

Once it's working, though, shipping updates is as simple as npm run build. The app bundles itself into a .dmg file ready for distribution.

Challenge #3: OAuth Debugging

Getting PKCE right took trial and error. I had to:

  • Add the app to Google's OAuth consent screen

  • Add my test email as an authorized user

  • Debug the redirect URI (it must be 127.0.0.1, not localhost)

  • Handle the authorization code properly

But once it worked, the rest was straightforward.

Key Takeaways

1. You don't need a backend for everything

OAuth PKCE is designed for desktop/mobile apps. You can authenticate users without running a server. Your app is the server for 5 seconds.

2. Local-first is underrated

Most indie developers assume they need to sync to the cloud. You don't. SQLite on your machine is powerful. Sync it manually, or add cloud backup without locking users in.

3. Tauri is a game-changer for indie developers

Electron dominates because it's easy. But Tauri is the better choice for native performance, small bundle size, and fast startup. The ecosystem is young but growing.

4. One weekend is enough

I built the MVP — tasks, due dates, priorities, GCal sync, native UI — in about 12 hours. Most of my time went to learning Tauri's architecture and OAuth, not coding.

How to Use It

  1. Download the latest .dmg from the Releases page

  2. Drag Daily Planner.app into Applications

  3. Launch it (you may need to right-click and "Open" to bypass Gatekeeper)

  4. Click "Connect Google Calendar" and authorize

  5. Start adding tasks

All data stays on your machine. You own it.

Contributing

The codebase is open source. If you want to:

  • Fix a bug

  • Add a feature

  • Improve the UI

  • Optimize performance

...you're welcome to contribute. The tech stack is accessible: Rust isn't scary, and React/TypeScript is familiar to web developers.

What Would You Build?

If you're frustrated with subscription pricing on productivity tools, what feature would you want in your ideal task manager?

Drop a comment below, and if it resonates, I'll add it to the roadmap.


Daily Planner is free and always will be.

No ads. No tracking. No pricing tiers. No dark patterns. Just a task manager that syncs to your calendar and respects your data.