Chronode - Project Time Tracking

Return to Projects
Chronode - Project Time Tracking

An automatic time tracking application for macOS developers. Monitors IDE activity and detects projects without manual timers or plugins. Tracks coding sessions, provides statistics and productivity insights, and exports data to CSV, PDF, and JSON formats.

Project Type
macOS App
Current Status
Published
App Version
1.0.1 (Build 2)
Development
203 hrs (website & app)

The App I Didn't Plan to Build

I was putting the finishing touches on Kanodo, a native kanban board app for macOS, when a sobering thought hit me. I had no idea how many hours I'd actually spent building it. Sure, I had a rough estimate in my head, but nothing concrete. I wanted to know the real number, the actual time investment.

So naturally, I did what any developer would do. I went looking for a time tracking app. Something simple that could monitor which apps I was using and for how long. Maybe throw in some basic reporting so I could see where my time was actually going.

What Was Already Out There

What I found was disappointing. Every app I looked at required one of two things that I simply didn't have patience for:

  • Install plugins for each application you wanted to track
  • Manually start and stop timers throughout your day

I don't have time to track time. That's the whole point of automation, isn't it?

The Decision

I wanted something dead simple. Install it, pick the apps to monitor, and let it do its thing. Some charts would be nice. Export to CSV for invoicing. That's it.

When I couldn't find what I needed, I made what might have been a questionable decision. I paused development on Kanodo and started building Chronode instead. In my defense, I genuinely thought it would be a quick project. A couple of weeks, tops. How hard could a menu bar app be?

What I Knew Going In

My Technical Background

My experience with Swift and Xcode was limited at this point. I'd only worked on Kanodo, which admittedly was quite feature rich and complex in its own right. But I'd managed to build something substantial with it, and that gave me confidence. The skills I'd picked up felt transferable. Anything I didn't know, I could learn along the way.

Portable Skills from Laravel

As a Laravel developer by trade, I was familiar with the general patterns:

  • Backend logic and business rules
  • Data persistence and migrations
  • API design and routing
  • Testing and validation

These concepts don't really change that much between languages. The syntax is different, sure, but the thinking is the same. Or so I told myself.

The Initial Build

The First 2.5 Weeks

The core functionality came together reasonably quickly. About 2.5 weeks to build the main features, write unit tests, and make sure everything worked reliably. SwiftUI made the interface straightforward, and Core Data handled persistence without too much drama.

The menu bar integration was simple enough. NSWorkspace provided all the notifications I needed to track app launches, terminations, activations, and deactivations. Handling edge cases like force quits, system sleep, and app crashes required some extra attention, but nothing too terrible.

Chronode Activity report

Documentation and Marketing

Then came the documentation and marketing website. Another 2 weeks to build that out properly. I wanted comprehensive docs because I hate using apps that don't explain themselves well. The website needed to clearly communicate what Chronode did and why anyone should care.

At this point, I was feeling pretty good about the whole thing. The app worked. The website looked professional. I was ready to ship. That's when reality introduced itself.

The Sandbox Revelation

The Rule I Didn't Know

Here's something I wish I'd known before I started this entire project. macOS apps that need Accessibility API access cannot be sandboxed. And apps that aren't sandboxed cannot be distributed through the Mac App Store.

Let that sink in for a moment.

Chronode absolutely requires Accessibility permissions to detect which project you're working on by reading window titles and interacting with running applications. Without those permissions, it's just a very expensive way to detect app launches, which you could do with a shell script.

So the App Store was out. Completely off the table. I had to pivot to self publishing.

The Cascade of Extra Work

This decision cascaded into weeks of additional work I hadn't planned for:

  • Payment processing through Stripe (custom implementation)
  • Webhooks to handle purchase verification
  • API for licence activation and validation
  • Device fingerprinting for activation limits
  • 90 day verification system with grace periods
  • All the infrastructure to support self publishing

The Laravel website I'd built for marketing suddenly became critical infrastructure. I added all of this myself. The payment flow, the webhook handlers, the licence API. All custom work because I couldn't use Apple's in-app purchase system.

The Honest Assessment

Looking back, if I'd known from the start that unsandboxed apps couldn't go in the App Store, I probably wouldn't have started this project at all. I would have just made do with whatever imperfect solution I could find. It ended up being significantly more work than I expected for what is essentially a small menu bar utility.

Project Detection: The Real Challenge

The Deceptively Simple Problem

If you asked me what the hardest part of building Chronode was, I wouldn't hesitate. Project detection. Hands down.

The problem is deceptively simple. You want to know which project someone is working on, not just which app they're using. If I'm in Xcode working on Project A, that time should be tracked separately from when I'm working on Project B in the same app.

Why AppleScript Failed

My first instinct was to use AppleScript to query the running applications for their current document or project. This worked for exactly nobody. Well, maybe one or two apps. But most modern IDEs don't expose that information through AppleScript in any reliable way.

Chronode Project distribution

Window Title Parsing: Harder Than It Looks

So I fell back to parsing window titles. This sounds straightforward until you realise that window titles are a complete free for all:

  • Every IDE has its own convention
  • Some put the project name in square brackets
  • Some put it at the end after a dash
  • Some put it at the beginning
  • VS Code likes to show you the entire file path plus the workspace name plus remote connection indicators plus probably your horoscope if you look hard enough

The Non-Project Window Problem

Then there are the windows that aren't projects at all:

  • Settings windows
  • About dialogs
  • Search results
  • Plugin marketplaces
  • Find and replace modals

Every IDE has dozens of these utility windows that would pollute the project list if you tracked them naively.

The Solution: Modular Detectors

I built a modular detection system where each IDE gets its own detector class. Each detector knows that IDE's quirks, what to look for in window titles, what to ignore, how to validate that something is actually a project and not just a random window.

For Xcode, I filter out anything that doesn't reference an xcodeproj or xcworkspace file. Individual source files aren't projects. Neither are build logs or debugger windows.

PhpStorm and the other JetBrains IDEs use square brackets for project names, which helps. But they also use square brackets for other things, so you have to maintain lists of known non-project windows to exclude.

VS Code was probably the trickiest because of its verbose window titles. You have to parse through multiple segments, handle remote development indicators properly, and figure out when the workspace name actually represents the project versus when it's showing you a specific file.

Trial, Error, and AI-Assisted Research

Getting all of this right took a lot of trial and error. I'd test with different projects, different IDEs, different workflows. Every time I thought I had it nailed down, I'd discover another edge case that needed handling.

AI tools helped tremendously with the research phase. Understanding the potential window title formats for 11 different IDEs would have taken forever without assistance. But the actual implementation and filtering logic was all manual work, testing, and refinement.

Chronode focus time report

What I Support Now

The system I ended up with supports these IDEs, each with its own detector and filtering rules:

  • Xcode
  • VS Code
  • PhpStorm
  • IntelliJ IDEA
  • PyCharm
  • WebStorm
  • Android Studio
  • Sublime Text
  • And a few others

The StoreKit Pivot

About midway through development, I made a significant architectural decision that I later had to completely reverse.

I integrated StoreKit 2 for in-app purchases. Spent time learning the framework, implementing the purchase flow, testing the sandbox environment. It was actually quite elegant once you understood the asynchronous nature of transactions.

Then I learned about the sandbox requirement for App Store distribution.

All that StoreKit work became instantly useless. I ripped it all out and built the custom Stripe integration instead. Not the most efficient use of development time, but at least I learned something about StoreKit that might be useful on a future project.

The Release Process Nightmare

The Self-Publishing Checklist

Self publishing an app turns out to be substantially more complicated than I expected. The requirements:

  • Sign everything with a Developer ID certificate
  • Notarize with Apple so users don't get scary warnings
  • Package everything properly
  • Build an update system

Sparkle and Dynamic Appcast Generation

For updates, I used Sparkle, which is the de facto standard for Mac app updates. It's a solid framework, but it requires you to generate EdDSA signatures for each release, manage appcast XML files, and make sure all the metadata is correct.

I decided to dynamically generate the appcast XML rather than manually maintain it. The Laravel backend now generates it on the fly based on what's in the database. This ensures it's always current and reduces the chance of human error.

The Build Script

The build script I wrote handles the complete release process:

  1. Builds the app in release configuration
  2. Exports and signs it with my Developer ID
  3. Creates a DMG
  4. Signs the DMG
  5. Uploads it to Apple for notarization
  6. Waits for approval
  7. Generates the Sparkle signature
  8. Outputs all the information needed to update the database

Notarization: The Waiting Game

The notarization process is particularly fun because you submit your app to Apple, wait for them to scan it for malware and security issues, and hope nothing fails. If something goes wrong, you get to debug cryptic error messages about code signing or entitlements.

I documented the entire release process thoroughly because I knew I'd forget half of it by the next release. The documentation covers everything from certificate setup to notarization credentials to database updates. Future me will appreciate it.

Chronode heatmap report

Features That Actually Worked Well

The Smooth Parts

Not everything was a struggle. Some parts of the app came together exactly as I hoped.

The Core Data persistence worked reliably from the start. SwiftData made the models clean, and the MVVM architecture kept everything organised. Sessions, projects, and app tracking all stored properly without corruption or loss.

Real-Time Updates

The real-time updates in the UI were satisfying to implement. All the charts update live while you work. If you have the app open, you can watch your time accumulate as you code:

  • The calendar heatmap fills in
  • The trend line grows
  • The session duration updates
  • Everything reflects your current activity

It creates a nice feedback loop.

Six Chart Types

I built six different chart types to visualise time data from different angles:

  1. Daily activity shows which days you're most productive
  2. Hourly productivity reveals your peak performance hours
  3. Project distribution breaks down where your time actually goes
  4. Trend analysis plots your work over time
  5. Calendar heatmap mimics GitHub's contribution graph
  6. Focus sessions categorises your work sessions by duration

Each chart includes both historic data and currently active sessions. This was important because I wanted users to see accurate, up to date information without waiting for sessions to complete.

Flexible Exports

The export system supports CSV, PDF, and JSON formats with privacy controls to anonymize project names and round timestamps when needed. This makes it practical for invoicing or sharing reports with clients without exposing sensitive details.

What I'd Do Differently

The Honest Truth

In all honesty, now that I know unsandboxed apps can't be offered in the App Store, I probably wouldn't have started this project. That sounds harsh, but it's true. The additional complexity of self publishing, payment processing, licence management, and custom update systems added weeks to what I thought would be a simple project.

Chronode hourly report

Technical Decisions

If I were to start over knowing everything I know now, here's what I'd change:

Project Detection Approach: I'd architect this differently from the beginning. I spent time trying to make AppleScript work when I should have jumped straight to window title parsing and AXUIElement inspection. That would have saved several days of false starts.

Modular Detector System: The system I eventually built is good, but I could have gotten there faster with better planning. Knowing the full scope of what each IDE would require upfront would have helped.

Documentation Estimates: I underestimated how much work the marketing website and documentation would take. It's not technically difficult, but it's time consuming to write clear, comprehensive docs and build a website that properly explains what the app does and why it matters. This is important work, but it's easy to underestimate during initial planning.

Licence API Complexity: The custom licence API was more work than expected too:

  • Device fingerprinting
  • Activation limits
  • 90 day verification with grace periods
  • Proper error handling for offline scenarios

All necessary for a good user experience, but each piece took time to implement and test thoroughly.

The Final Product

What Got Built

Despite all the complications and unexpected work, Chronode turned out well. It does exactly what I originally wanted:

  • Tracks time automatically without any manual intervention
  • Provides meaningful analytics about work patterns
  • Exports data in useful formats
  • Respects user privacy by keeping everything local

Technical Achievements

The app supports any macOS application with intelligent project detection for 11 different IDEs. It runs efficiently in the menu bar using less than 1% CPU and under 50MB of memory. The UI is clean and gets out of your way. The 308 unit tests all pass, which gives me confidence in the reliability.

Infrastructure Built

I built a complete payment and licensing system:

  • Stripe integration for payments
  • Webhook handling for purchase verification
  • RESTful API for licence verification
  • Device fingerprinting and activation limits
  • 90 day verification with grace periods

The Laravel backend generates dynamic appcast feeds and handles all the infrastructure for self publishing.

Chronode projects list

The Documentation

The documentation is thorough. The website explains everything clearly. The release process is documented and scripted. Future updates will be significantly easier now that all the infrastructure is in place.

And most importantly, I finally know how long it took to build Kanodo. Well, approximately. Because I built Chronode partway through and lost track.

Lessons Learned

Building Chronode taught me several important lessons that will influence how I approach future projects.

Platform Restrictions Matter

Understand platform restrictions before committing to an architecture. The sandbox requirement for App Store distribution fundamentally changed the entire project. Knowing that upfront would have saved weeks of pivoting.

Parsing Real-World Data Is Hard

Parsing real world data like window titles is always harder than it looks. Every application has its own conventions and edge cases. Building robust detection requires extensive testing with actual usage patterns.

Self-Publishing Is Substantial Work

Self publishing a Mac app is substantially more work than using the App Store. You need:

  • Payment processing
  • Licence management
  • Update infrastructure
  • Notarization
  • Proper code signing

None of this is conceptually difficult, but it all takes time.

Documentation Takes Time

Documentation and marketing are essential but time consuming. Users need clear explanations of what your app does and why they should care. Future you needs detailed docs about your release process. Both require significant investment.

Chronode session list

Modular Architecture Pays Off

Modular architecture pays off for complex features. The IDE detector system would be a mess if everything was in one giant file. Separate detector classes for each IDE made it manageable to handle 11 different applications with their unique quirks.

Accidental Projects Teach the Most

Finally, sometimes the projects you don't plan to build teach you the most. I learned about:

  • Accessibility APIs and window management
  • AppleScript limitations
  • Code signing and notarization
  • Payment processing and webhooks
  • Licence verification systems
  • Dynamic appcast generation
  • A dozen other macOS development topics that will be useful going forward

The Reality of Estimates

Chronode took about 5 to 6 weeks total instead of the 2 weeks I initially estimated. That's not unusual for software projects, but it's a useful reminder:

  • Initial estimates are usually optimistic
  • Complex features like project detection always take longer than you think
  • Platform restrictions add unexpected work
  • Polish and documentation consume more time than anticipated

Would I Do It Again?

Would I build it again knowing everything I know now? Probably not. But I'm glad it exists. It solves a real problem that I had. It works reliably. Other developers seem to find it useful. And I learned a tremendous amount in the process.

Plus, now when someone asks how long it takes to build a menu bar app, I have a very specific answer. Longer than you think.

Expanding Beyond Developers

The Realisation About Market Size

Towards the end of development, something important dawned on me. The target audience for an IDE time tracking app was incredibly narrow. Developers. That's it. Sure, it's a market I understand well, but it's also quite limited.

I'd spent all this time building project detection for 11 different IDEs, creating a solid product that solved a real problem. But only for people who write code. What about designers working in Figma? Video editors using Final Cut Pro? Writers working in Scrivener? Anyone who spends time on their Mac doing focused work could benefit from automatic time tracking.

Making It Universal

So I pivoted again. I added functionality to track time for any application on the user's Mac, while still maintaining the intelligent project detection for supported IDEs. Now Chronode works for anyone, regardless of their profession.

A graphic designer can track time spent in Photoshop and Illustrator. A video editor can monitor hours in Premiere Pro. A writer can see how much time they spend in their writing apps versus research browsers. The app doesn't care what you do. It just tracks your time automatically.

The IDE project detection remains for developers who need that granularity. But now the app has a much broader appeal.

Chronode trends report

Future Plans

For next year, I'm considering adding project detection support for non-IDE applications. Graphic design apps and video editing software could potentially be tracked by project file names. I'm not entirely sure what the implementation will look like yet, but the goal is clear. Make project-level tracking available beyond just development work.

What started as a tool specifically for developers evolved into something anyone can use. It's a better product for it, even if it meant more work to get there.

The UserDefaults Security Lesson

A Hard Truth About Local Storage

The app is mostly finished now. I just need to do additional testing on the licensing API system. But I learned something important while building my next app, Audibar, that forced me to rethink part of Chronode's licence verification.

I had originally implemented a grace period system for licence verification. The app would check with the server on first launch, then verify again every 90 days. If the verification failed, it would allow up to 5 failed attempts before revoking Pro status. This seemed user friendly for people who might be offline or having network issues.

The problem? Users can modify UserDefaults directly. They're just plist files on disk. Anyone with basic technical knowledge can change the values to bypass verification entirely.

Always Verify on Launch

This means I can't rely on UserDefaults to securely track when the last verification happened or how many attempts have failed. I have to verify the licence every time the app launches. No grace periods. No trust in local storage.

This is actually standard practice, similar to how StoreKit 2 works. I just didn't know it when I initially designed the system. The verification is quick and happens in the background, so it doesn't impact the user experience. But it ensures that licence status is always accurate and up to date.

It's a good reminder that any data stored locally can be modified. If it's security critical, you can't trust it. You have to verify with the source of truth, which in this case is the licensing API on the server.

This lesson will save me time on future projects. Better to learn it now than discover it after launch when someone inevitably figures out the workaround.


Comments (0)

Optional. If given, is displayed publicly.

Optional. If given, is not displayed anywhere.

Required with 25 characters minimum

Comments are reviewed for spam and may require approval.

No comments yet. Be the first to share your thoughts!