Cal: A Local-First Desktop Calendar Assistant

Inspiration

Cal started from a problem I kept running into in my own life. Recently, I have had a lot of tasks, appointments, deadlines, and other things to keep track of, but I often do not write them down. It is not because they are unimportant. Usually, the problem is that adding something to a calendar feels like more work than it should.

I have to stop what I am doing, open a calendar app, find the right date, fill out several fields, and organize everything manually. It is not a huge amount of effort, but it is often enough for me to think, “I’ll remember it later.” Sometimes I do. Sometimes I don't.

That made me think voice could be a much better way to capture things. Saying, “Remind me to submit this on Friday,” or “Add a dentist appointment next Tuesday at three,” is much faster than opening a full calendar application and entering everything by hand.

At the same time, local language models have become capable enough to understand casual instructions, interpret dates, ask follow-up questions, and turn a spoken request into a structured action. That made the idea feel realistic.

Cal is my attempt to build a lightweight, voice-controlled desktop calendar that feels more like a small personal secretary than a traditional productivity app.

The main goal is simple: reduce the amount of friction between remembering something and actually putting it on your schedule.

What Cal Does

Cal is a lightweight desktop calendar and agenda widget built around quick interactions.

It allows users to:

  • View upcoming events in a compact agenda.
  • Create, edit, move, and delete events.
  • Add events using natural language.
  • Use push-to-talk voice commands.
  • Receive local desktop reminders.
  • Hear optional spoken responses from the assistant.
  • Keep calendar data and AI processing local whenever possible.

I did not want Cal to become another large calendar platform with dozens of views, collaboration tools, menus, and account settings. Those products already exist.

Instead, Cal focuses on the things I would actually want to do throughout the day:

  • Check what is coming up.
  • Add something before I forget it.
  • Move an event quickly.
  • Ask what is on my schedule.
  • Get reminded at the right time.
  • Do all of that without opening a full calendar app.

How I Built It

I built Cal as a cross-platform desktop application using Tauri, with a web-based frontend and a native Rust application layer.

Tauri made sense because it gave me access to native desktop features while still letting me build the interface with familiar frontend tools.

Cal uses desktop features such as:

  • A system tray.
  • Global keyboard shortcuts.
  • Desktop notifications.
  • Custom frameless windows.
  • Launch-at-startup behavior.
  • Local database and file access.
  • Separate utility windows for workflows that do not fit inside the compact widget.

The frontend is built with TypeScript and a component-based UI. I designed the main screen around an agenda-first layout instead of a large month view because I wanted the user to immediately see what matters now and next.

For local storage, Cal uses SQLite. Events, reminders, recurrence settings, preferences, and assistant actions can all be stored directly on the device without requiring an account or a remote backend.

The trusted application logic lives in Rust rather than directly in the frontend. The interface sends typed commands to the native layer, where Cal can validate them before changing calendar data.

A scheduling request follows a flow like this:

User enters or speaks a request
    ↓
Cal extracts the title, date, time, and other details
    ↓
A structured event proposal is created
    ↓
The user sees a preview
    ↓
The user confirms, edits, or cancels
    ↓
The validated event is saved

This became especially important once I began adding voice and AI features. Even a capable model can misunderstand a date, select the wrong event, or make an incorrect assumption. Because of that, the assistant should propose actions rather than directly changing the calendar.

Visual Design

A large part of the project was figuring out how Cal should look and feel.

I wanted the app to feel lightweight, modern, and almost like it was floating above the desktop rather than sitting inside a traditional application window. I focused heavily on giving it a glass-like appearance using transparency, soft borders, subtle shadows, layered surfaces, and blurred backgrounds.

The goal was not to make every part of the interface highly decorative. I wanted a restrained glassmorphism style that still felt readable and practical.

That meant balancing:

  • Transparency without losing contrast.
  • Blur without making text difficult to read.
  • Rounded surfaces without making the interface feel overly soft.
  • Shadows that created depth without looking heavy.
  • Minimalism without making the app feel empty.
  • Visual polish without distracting from the calendar itself.

The glass look worked well with the idea of Cal being a desktop widget. It helped the application feel less like a full-screen productivity tool and more like a small, always-available layer on top of the desktop.

Local AI and Voice

One of the main goals for Cal is to make voice input useful without requiring a large cloud-based AI service.

For common scheduling instructions, Cal can rely on deterministic parsing instead of sending every request through a language model.

Commands such as:

  • “Add lunch with Alex tomorrow at noon.”
  • “Move my dentist appointment to Friday.”
  • “What do I have this afternoon?”
  • “Create a focus block every weekday at nine.”

can often be converted directly into structured actions.

A small local model can then act as a fallback for requests that are more ambiguous or require basic reasoning.

The local model is not intended to act as an unrestricted agent. It receives a limited set of permitted calendar actions and returns structured output. The Rust application validates that output before anything is changed.

For voice input, the plan is to use local speech-to-text so recordings do not need to leave the user’s device.

For spoken responses, I am integrating Kokoro 82M, a small local text-to-speech model that can produce natural-sounding speech without relying on a cloud API.

The text-to-speech pipeline is designed to stream responses:

The local model begins generating text
    ↓
Cal detects a complete phrase or short sentence
    ↓
Kokoro begins generating audio
    ↓
Playback starts
    ↓
The next phrase is synthesized in parallel

This should make the assistant feel much more responsive than waiting for the entire answer to finish before speaking.

Users will also be able to turn voice responses on or off. They can choose whether Cal should speak after voice commands, typed commands, confirmations, or reminders.

My Development Process

A large part of Cal was built through an AI-assisted development workflow.

I usually started with a planning session using GPT-5.6 in Pro mode. I used those sessions to work through the product direction, architecture, technical specifications, edge cases, and implementation details.

Once the plan felt solid, I had it create a more focused prompt for Codex to execute.

My workflow generally looked like this:

  1. Work through the feature or problem with GPT-5.6.
  2. Refine the technical plan.
  3. Create a detailed implementation prompt for Codex.
  4. Let Codex make the changes.
  5. Run the application and test it manually.
  6. Write down everything that felt wrong, confusing, or unfinished.
  7. Turn that list into another focused prompt.
  8. Repeat.

This worked well for major structural changes, but the smaller details were much harder.

A prompt can describe the main layout, the required behavior, and the general visual direction, but it is difficult to fully communicate things like spacing, proportions, hierarchy, animation timing, or whether a screen simply feels awkward.

Because of that, manual testing remained a major part of the process.

Challenges

Communicating the Product Direction

One of the hardest parts was making sure Codex understood what I wanted Cal to become.

The broad concept was easy enough to explain: a lightweight, local-first, voice-controlled calendar widget.

The difficulty was in the details.

There were many cases where the result was technically correct but still did not feel right. A layout might include every required control but feel crowded. A modal might work at a normal window size but become unusable in widget mode. A window might appear rounded internally while the real native window still had square outer corners.

Those kinds of problems are difficult to communicate in one large prompt.

I learned that broad prompts are useful for architecture and major features, but visual polish usually requires smaller, more targeted iterations.

UI and Visual Design

UI and visual design were some of the biggest challenges because they are not my main areas of expertise.

I often knew that something looked wrong without immediately understanding why it looked wrong or what design principle would fix it.

While working on Cal, I learned more about visual styles such as:

  • Minimalism.
  • Maximalism.
  • Glassmorphism.
  • Neomorphism.
  • Skeuomorphism.
  • Brutalism.
  • Spatial UI.
  • Claymorphism.

This helped me think more clearly about depth, contrast, surfaces, borders, spacing, hierarchy, and how much visual decoration an interface should have.

For Cal, I kept coming back to a combination of minimalism and glassmorphism. The application is meant to stay visible on the desktop without becoming distracting, so the interface needed to feel light and polished without becoming overly decorative.

Designing for Widget and Full-App Modes

The compact widget view works well for checking an agenda, but it does not have enough room for every workflow.

One of the clearest examples was the event editor.

Initially, the full edit-event form opened inside the widget window. It became compressed, heavily scrolled, and difficult to understand. The footer took up too much space, the fields were squeezed together, and the entire experience felt unusable.

At first, I treated this as a responsive-design problem. Eventually, I realized the full editor should not be forced into the widget at all.

The improved approach uses different interaction patterns depending on the context:

  • In normal application mode, the editor opens as a modal.
  • In widget mode, the editor opens in a separate utility window.
  • A future quick-edit popover can handle small changes.
  • More advanced settings remain in the full editor.

This taught me that not every workflow should be compressed into every screen size.

Sometimes the correct responsive design decision is to change the interaction model entirely.

Native Desktop Window Behavior

Another challenge was making the Tauri application feel like a real desktop widget.

At one point, the content had rounded corners, but the underlying native window was still square. The result looked like a rounded application sitting inside a rectangular frame.

Fixing that required changes in both the native window configuration and the frontend styling:

  • The Tauri window had to be transparent.
  • The HTML document background had to be transparent.
  • The visible background and border radius had to move to the correct root container.
  • Shadows and resizing had to be tested across platforms.

This was a good reminder that desktop applications often require work across both frontend and native layers.

Reliable Reminders

Reminders seem simple until the application restarts, the computer goes to sleep, or the system clock changes.

A basic in-memory timer is not reliable enough for a calendar.

Cal needs to handle:

  • Application restarts.
  • Launching at login.
  • Computer sleep and resume.
  • Reminders that become due while the system is suspended.
  • Time-zone changes.
  • Daylight-saving transitions.
  • Duplicate notification prevention.
  • Recurring events and exceptions.

That means pending reminders need to be reconstructable from persistent storage instead of existing only as temporary timers.

This part of the project required much more systems thinking than I initially expected.

Natural-Language Time

People describe time in inconsistent ways.

Examples include:

  • “Tomorrow afternoon.”
  • “Next Friday.”
  • “In an hour.”
  • “Move it back half an hour.”
  • “The second Tuesday of next month.”

The difficult part is not only extracting a date. It is recognizing when the phrase is ambiguous.

For example, “next Friday” may not mean the same thing to every user.

Cal therefore needs to recognize uncertainty and ask a follow-up question instead of pretending every interpretation is correct.

Voice Latency

Voice assistants feel slow when every stage waits for the previous stage to finish.

The full path includes:

Microphone input
→ speech recognition
→ command interpretation
→ local model reasoning
→ response generation
→ text-to-speech
→ audio playback

Even if each individual component is reasonably fast, the total delay can still feel noticeable.

I learned that perceived latency depends heavily on streaming and concurrency.

Speech recognition can process audio incrementally. The model can generate text gradually. Text-to-speech can begin once the first useful phrase is available.

I also learned that smaller chunks are not always better. Sending one or two words at a time may reduce delay, but it usually creates worse pronunciation, awkward pauses, and unnatural intonation.

The better balance is to synthesize short phrases or sentences while the rest of the response is still being generated.

Development Environment

I also learned that WSL is useful, but desktop application development would probably be easier in a more native environment.

WSL works well for web development, Rust, Git, and command-line workflows. However, Cal also depends on:

  • Native windows.
  • Audio input and output.
  • Desktop notifications.
  • Platform-specific packaging.
  • Filesystem behavior.
  • Application startup.
  • System tray integrations.
  • Cross-platform testing.

Those areas can become more complicated when the development environment is separated from the operating system where the application is actually running.

WSL is still workable, but this project made me appreciate the value of developing and testing more directly on the target platforms.

What I Learned

One of the biggest lessons was that I need more deliberate and well-defined testing pathways.

My current process relies heavily on opening the app, clicking through different workflows, resizing windows, and writing down whatever feels wrong.

That approach is useful, especially for visual and usability issues, but it can also be inconsistent. It is easy to repeat the same paths and still miss important edge cases.

For future development, I want clearer test scenarios for each major workflow, including:

  • Creating an event with text.
  • Creating an event with voice.
  • Editing an event in normal mode.
  • Editing an event in widget mode.
  • Cancelling a spoken response.
  • Resuming after sleep.
  • Delivering a reminder after restart.
  • Handling ambiguous dates.
  • Switching between light and dark themes.
  • Resizing the app to its minimum supported dimensions.

I also learned a lot about the tradeoff between model capability, speed, and cost.

GPT-5.6 Sol was often much better at understanding what I meant instead of only following the literal wording of a prompt. It was especially useful for architecture, technical planning, large refactors, and situations where the overall product direction mattered.

At the same time, it was slower, more expensive, and more time-consuming.

In some cases, it would wait on CI/CD pipelines or other validation steps, which made the iteration cycle even longer.

That taught me to be more selective about when I use it.

Sol is most useful when the task requires deeper reasoning, intent recognition, or a complete technical plan. Smaller models or more narrowly scoped Codex prompts are usually better for straightforward changes.

I also learned that AI-assisted development does not remove the need for human judgment.

Codex can implement a clearly defined plan very quickly, but it cannot replace actually using the product and noticing the small details that are difficult to explain in advance.

The quality of the final application still depends on:

  • Defining the problem clearly.
  • Choosing the right architecture.
  • Testing realistic workflows.
  • Recognizing when something feels wrong.
  • Turning vague feedback into specific fixes.
  • Continuing to refine the result until it matches the intended experience.

Finally, I learned that lightweight applications are not necessarily simple applications.

Even a small calendar widget has to deal with:

  • Native window behavior.
  • Persistent storage.
  • Dates and time zones.
  • Recurring events.
  • Background reminders.
  • Accessibility.
  • Local models.
  • Audio processing.
  • Cross-platform packaging.
  • Privacy and security.

The interface may be small, but the system behind it still needs to be reliable.

What Is Next

The next major steps for Cal are:

  • Improving recurrence and time-zone handling.
  • Completing the persistent reminder scheduler.
  • Adding calendar import and export.
  • Integrating local speech-to-text.
  • Adding Kokoro-powered spoken responses.
  • Benchmarking lightweight local models for scheduling commands.
  • Improving streaming and interruption latency.
  • Defining better automated and manual testing pathways.
  • Refining the widget experience across Windows, macOS, and Linux.
  • Exploring optional synchronization with external calendar providers.

The long-term goal is for Cal to feel like a quiet desktop assistant rather than another productivity dashboard.

It should always be nearby, quick to use, private by default, and capable of helping users manage their schedule with as little friction as possible.

(I thought I would name it Cal at first because "Calendar", but honestly it's more like an agenda).

Built With

  • icalendar
  • linux
  • macos
  • ollama
  • ollama-http-api
  • reqwest
  • rfc-5545
  • rust
  • serde
  • sqlite
  • sqlx
  • svelte-5
  • sveltekit
  • tauri-2
  • tauri-global-shortcut-api
  • tauri-notification-api
  • tokio
  • typescript
  • vite
  • webview
  • whisper.cpp
  • windows
Share this project:

Updates

posted an update

Haha, looks like I didn't get it fully working in the end. I think it had something to do with failing to fully install/run the local llm model. If there's anything I learned it's get the mvp down first, small UI/UX things aren't that big of a deal.

Log in or sign up for Devpost to join the conversation.