← Back to RhyGPU

OmniPlanner Devlog

Building a Life OS

Local-first planning, calendar AI, habit tracking, and the plan-vs-actual gap

February 2026 – Present · 70 commits · 22 phases · ~11K lines of TypeScript

Electron Desktop Week-Isolated Planning Habit Engine Calendar AI Email Integration Local AI AGPL-3.0

I. Before the Repository

It started as a web app. Vite, React, a weekly planner with tabs for goals and habits. The usual stuff. But the data lived in localStorage inside a browser tab, and the whole thing disappeared if you cleared your cache. That felt wrong for something meant to hold your life plan.

The question was: what if a planner was a real app? Something you could trust with years of goals, habits, and history. Something that never sends your data to a server. Something that works on your machine, on your terms.

Electron was the fastest path. Tauri was considered, but Electron's ecosystem for IMAP, notifications, and cross-platform builds was more mature. The first commit converted the web app into a desktop app in a single day.

The first commits were not glamorous. electron-main.cjs, preload.cjs, run.js launcher, electron-builder config targeting Windows NSIS, macOS DMG, and Linux AppImage. AGPL-3.0 license. GitHub Actions CI that builds executables on every push.

Same day: Gemini AI provider wired in. Dead code removed. The project had a protected boundary.

II. The Week Is an Island

The core architectural bet: week-isolated planning. Each week is an independent page. Goals, todos, habits, calendar events — all scoped to that week. Changing this week doesn't affect last week or next week. Nothing bleeds.

This sounds simple. It's not. Most planners treat time as a flat list. OmniPlanner treats each week as a bounded context with its own goals, its own daily plans, its own habit tracker. The WeekData type became the atom of the system.

Phase 2 introduced GoalItem: structured life goals at 10Y, 5Y, 3Y, 1Y, monthly, and weekly horizons. Legacy text blobs were migrated into typed records. Weekly goals could link upward to life goals via Todo.parentGoalId.

Phase 4 hardened credentials. services/storage/secureSettings.ts became the single boundary for API keys and IMAP passwords. Electron.safeStorage encrypted everything at rest.

The storage layer was abstracted behind a StorageAdapter interface. LocalStorageAdapter for Electron, IndexedDBAdapter for web/PWA. Swappable without touching domain logic. Schema migrations ran automatically on startup.

III. Calendar as Source of Truth

A planner without a calendar is a todo list. A calendar without a planner is a schedule. OmniPlanner needed both, connected.

Phase 5 added CalendarEventKind: meetings, focus blocks, task blocks, and routines. Each could link to a goal and a todo. The weekly grid showed time-blocked events as colored bands.

Phase 6 added execution analytics: the weekly review sidebar showed focus block count, goals with time support, completed linked tasks, and unscheduled work that still needed blocks.

Email integration came next. IMAP connection with Gmail, Outlook, Yahoo, Naver. Emails could be read inline next to calendar events. One-click "Create Event from Email" parsed meeting requests into calendar blocks.

Email errors got a full taxonomy: EMAIL_AUTH_FAILED, EMAIL_IMAP_DISABLED, EMAIL_APP_PASSWORD_REQUIRED, EMAIL_NETWORK_TIMEOUT. Each with a calm, user-facing message and an operation ID for correlating logs. No raw credential leakage in error messages.

IV. Hardening

138 tests across 7 files. Backup validation. Migration idempotency. Onboarding detection. The app had to survive real use — corrupt storage, failed imports, users who clear their cache.

Phase 12 added conflict-free restore: preview backup metadata before overwriting, handle legacy formats, survive mid-migration crashes.

Phase 19 was the cleanup pass: strict typecheck with zero errors, ID normalization from number to string, component extraction, AI API cleanup.

Phase 22 added OAuth token lifecycle: safe refresh, re-auth on expiry, IMAP preserved as fallback.

V. The Main Screen Problem

The weekly planner was solid. Habits worked. Calendar connected to email. But the main screen was a grid — and nobody opens a planner to see a grid. They open it to know what's next.

The planner was complete. The entrance was wrong.

So the main screen became a Dashboard. Today's top events ranked by priority × urgency. Habits due now with one-tap check-off. Recent emails. Top todos. Quick-add forms for new todos and new habits.

Every event card has a Start button. Tap it when the event begins — the app logs your actual start time. Skip marks it missed. Snooze reminds you in 5 minutes. Sleep events get "Going to bed" instead of "Start".

Priority stars (1-5) on every event and todo. Manual priority always overrides the default heuristic. Urgency (due soonest) fills in when you haven't set priority. The score is priority × 20 + urgency.

All mutations flow through setAllWeeks → Weekly Planner, Monthly View, and Goals View all see the same data. No sync needed because there's one source of truth.

VI. Pulse — The Alarm System

A calendar event at 11pm should auto-create a wind-down alarm at 10pm and a wake alarm at 6am. A meeting at 2pm should auto-create a prep alarm at 1:30pm. Not manually. Automatically.

The alarm rules engine derives alarms from calendar events. Each rule has an event kind filter, an offset in minutes (negative = before, positive = after), and a template string. The calendar is the single source of truth for alarms.

The Pulse tab shows: daily reminder toggles (morning planner, habit check-in, focus block alert), upcoming event-derived alarms, and sleep alarm rules. Master enable/disable at the top.

Mobile notifications fire via @capacitor/local-notifications. Desktop notifications are the next piece.

138 tests still pass. Typecheck clean. Electron 42 (upgraded from 26). Auto-backup on app exit. Window state persistence. GitHub release version checking. Corrupt storage auto-repair.

VII. What's Next

The plan-vs-actual overlay on the weekly grid. Soft translucent blocks for what you planned. Solid opaque blocks for what actually happened. Per-day toggle to compare.

Desktop notifications via Electron's Notification API. Rich alerts with Start/Skip/Snooze actions baked into the notification itself.

Wearable and lock screen widgets. "What's next?" on your wrist without opening the app.

AI ranking: when an AI provider is configured, the dashboard ranking learns from your patterns. "You always skip gym on Mondays. Want to reschedule?"

Covered Commits

This devlog covers the full OmniPlanner build from February to May 2026.

  1. 0534b6c Fixed infini load time/ clean up
  2. 2bc3be3 Correct OmniPlanner license notice
  3. 0434ebd Add AGPL license
  4. e0867c2 Phase 21-C: OAuth token lifecycle
  5. 3bec6fc Phase 21-B: OAuth login foundation
  6. 6ea2c38 Phase 22: email reliability and diagnostics
  7. 451faea Phase 21: test coverage and release confidence
  8. 24634e4 Phase 20: reminder visibility polish
  9. 326dcf0 Phase 19: engineering hardening
  10. 96ceb4d Phase 13: Mobile UX and daily-use polish
  11. 301a9d4 Phase 12: Offline resilience and conflict-free restore
  12. 890faae Phase 11: Native secure storage and reminder wiring
  13. 20c5d2c Phase 10: Capacitor mobile shell and notification service
  14. 1fa7885 Phase 9: web shell and local-first PWA
  15. 945e4c3 Phase 6: execution analytics and weekly review
  16. e435576 Phase 5: calendar execution and planning intelligence
  17. 3e79bbe Phase 3: goal-to-week linking via Todo.parentGoalId
  18. 62577c8 Phase 2: GoalItem domain model and migration v2
  19. 2485faf Phase 0+1: product docs and storage abstraction
  20. daf14d3 Add multi-AI provider system (Gemini, Claude, OpenAI)
  21. 4a74631 Convert from web app to local Electron desktop app
  22. 725ced4 Fix upload, Electron crash, AI service, and build issues

Next: the plan-vs-actual weekly overlay, desktop notifications, and AI-powered ranking.