Kanodo - KanBan Project Manager

Return to Projects
Kanodo - KanBan Project Manager

A native Kanban board application for macOS. Features workspaces, boards, columns, and cards with markdown support. Includes labels, file attachments, card dates, and mini-boards within cards for nested task management and more.

Project Type
macOS App
Current Status
In Progress
App Version
1.0.0 (Build 1)
App URL
Currently unavailable
Development
176 hrs (website & app)

The Problem with Existing Kanban Apps

I needed a proper kanban board for managing my development work. Something straightforward. Offline first. Native to macOS. Private. Under my control.

Trello exists, obviously. It's the kanban app everyone uses. But Trello has this frustrating habit of making you pay for basic customisation through additional widgets. The card windows are tiny with no way to resize them properly. And crucially, everything lives in the cloud. Your data sits on someone else's servers. You need an internet connection to be productive. That never sat right with me.

I wanted my files stored locally on my Mac. I wanted to work offline without restrictions. I wanted the power and features that a native macOS app could provide, not a web app pretending to be native.

The Decision to Build It

I could have made Notion work like a kanban board. Or Trello. Or any number of project management tools that sort of do kanban if you squint at them the right way. But I didn't want to force something else to work like a kanban app. I wanted an actual kanban app built for the way I work. So I decided to build one.

This would be my first proper macOS app. My first Swift project. My first time working with Xcode beyond basic tutorials. I'm a Laravel developer by trade, so the entire Apple development ecosystem was new territory. How hard could it be?

Starting from Scratch

Coming from Laravel to Swift and SwiftUI presented a significant learning curve. The language syntax was completely different. The type system worked differently. The entire paradigm of declarative UI took time to internalise.

What transferred well:

  • Architecture patterns and separation of concerns
  • Service layer design and dependency injection
  • Data persistence concepts (Core Data vs Eloquent)
  • Unit testing principles and practices
  • API design and structure

What was completely foreign:

  • Swift's type system and optionals
  • SwiftUI's declarative syntax
  • Property wrappers and state management
  • Core Data's relationship model
  • Xcode's quirks and build system

The language and syntax were the biggest challenges. But patterns like MVVM, dependency injection, and test-driven development translated across languages. Once I understood how Swift wanted me to think about problems, the solutions started making sense.

Building the Core Features

I made two architectural decisions early on that distinguish Kanodo from virtually every other kanban app.

Cards open in windows, not modals.

Almost every kanban app shows you a modal dialog when you click a card. One card at a time. You can't compare two cards. You can't reference one while editing another. It's limiting.

In Kanodo, cards open in actual windows. Multiple windows. You can have five card windows open simultaneously if you want. You can arrange them across multiple monitors. You can work on several cards at once. This uses the native windowing system that macOS provides, and it transforms how you actually work with cards.

Mini-boards within cards.

This is the innovation I'm most proud of. Inside each card, you can create a miniature three column kanban board. To Do, In Progress, Done. With its own cards that you can drag between columns.

As a developer, this is incredibly powerful. I create a card for a feature, then break that feature down into smaller tasks within the mini board. Each mini task can have its own notes and attachments. I drag them through the workflow as I make progress. It removes the need for traditional checklists, though I kept those as well because some people prefer them.

I haven't seen this anywhere else. It might exist, but I've never encountered it.

The Three Date System

Most kanban apps give you a single due date. I did research into what types of dates people actually needed and found consistent patterns in feature requests and complaints.

Kanodo supports three dates:

  1. Start Date - The earliest you can begin work
  2. Work Date - When you plan to actually start
  3. Due Date - The deadline

You can use all three, or just one, or none at all. The system calculates urgency based on which dates are set and shows colour coded indicators on cards. Overdue cards are red. Cards due today are orange. Coming up soon shows yellow. It's flexible without being complicated.

File Storage Strategy

All attachments can be stored locally or synced to iCloud. The iCloud integration is primarily for backup purposes. You can migrate files back and forth between local and cloud storage through the settings.

Files use UUID based naming for stability and are organised hierarchically by workspace, board, and card. When you delete entities, the cleanup service removes associated files and recursively deletes empty directories. Everything stays tidy.

The Drag and Drop Nightmare

Around early September, I hit a wall with drag and drop functionality. Sometimes it would work. Sometimes cards would disappear and reappear on reload. Sometimes the ordering would be completely wrong. The animations were janky. You couldn't tell where a card would actually land when you dropped it.

Cards between columns were particularly problematic. They'd vanish during the drag operation or appear in the wrong position. Same column reordering was unreliable. The whole system felt fragile.

The Solution

I completely rewrote the drag and drop system. Created custom drop delegates for each draggable type (cards, mini cards, columns, checklist items). Implemented position based reordering with proper Core Data persistence.

The breakthrough was adding ghost placeholders. While dragging a card, you see exactly where it will land. A placeholder appears in that position. The placeholder itself is a drop target, which makes the interaction intuitive. You're not guessing. You can see the result before you commit.

Created a global drag state manager to track the original column and the active drop column. This prevents cards from getting lost during cross column moves. Proper cleanup happens when you drag back to the original column or cancel the operation.

After the rewrite, drag and drop just works. Cards go where you expect them. The visual feedback is clear. The persistence is reliable. It's one of those things that should just work in the first place, but getting there required understanding the entire state lifecycle.

The Localisation Mistake

Early in development, I implemented a completely custom localisation system. Every single string in the app could be edited by the user. Non English speakers could translate the entire interface themselves. This seemed like a good idea at the time. Maximum flexibility. User empowerment. Community translations without waiting for official localisation.

Why It Was Wrong

The system became unmaintainable almost immediately. Every time I added a feature, I'd add new strings. Users who had customised their interface would suddenly see English text mixed with their translations. There was no way to add new keys without breaking existing customisation.

I was being lazy. I was avoiding the work of proper internationalisation by making users do it themselves. That's not a feature. That's a burden disguised as flexibility.

The Correct Approach

I ripped out the entire custom system and implemented standard iOS localisation with Localizable.strings files. Over 370 properly localised strings using the platform's native system. Support for different English variants (en, en-GB).

This is how localisation should work. Translations live in the codebase. Updates include translated strings. Users get a polished experience without having to maintain a translation database themselves. It was a valuable lesson. Sometimes the "clever" solution is wrong, and you need to just do it properly.

Unique Features That Emerged

The Service Layer

The app uses over 24 specialised services for different concerns:

  • PersistenceController for Core Data management
  • FileStorageService for local and iCloud file operations
  • WorkspaceDashboardDataService for analytics
  • DateService for date calculations and urgency
  • FilterService for board level filtering
  • WindowTrackingService for multi window management
  • DragStateManager for drag and drop coordination

Each service has a single responsibility. Each accepts dependencies through initialisation. Each can be tested in isolation. The architecture scales well as complexity grows.

The Dashboard System

I could have made workspace and board overviews simple lists. Just show the data. Basic. Minimal. Instead, I built full analytics dashboards.

The workspace dashboard shows:

  • Comprehensive statistics (boards, cards, storage usage)
  • Recent activity with filtering
  • Board distribution and completion rates
  • Card urgency indicators and schedules
  • Label usage analytics
  • Mini board progress visualisation
  • Calendar heatmaps

The more I learned about SwiftUI's capabilities, the more ambitious the dashboards became. Why settle for basic when you can provide genuine insights into how you're actually working?

Smart Search and Filtering

Global search works across all workspaces, boards, and cards. Board scoped search filters in real time as you type. The system uses Core Data fetch indexes for performance and implements debounced search with a 300 millisecond delay to reduce database queries.

Search results are organised into sections (cards, mini cards, attachments) with individual cell navigation. Click a workspace name and navigate there. Click a board and open it. Click a card and open that card window. Everything is clickable and navigable.

Board level filters let you show or hide cards based on properties (has attachments, has content, has mini boards) or by labels. Filters reset automatically when you navigate away or switch boards.

The Component Library

As the app grew, I started noticing the same patterns appearing in multiple places. Form fields with identical styling. Modal sheets with the same header structure. Buttons that all needed the same hover states and animations.

Rather than continue copying code between files, I extracted these patterns into reusable components:

  • TextInput for consistent form fields
  • FormSheet for modal dialogs
  • SelectBox for dropdowns
  • MultiSelect for multi selection with search
  • ColorPicker using HSB colour space (the native macOS picker always appeared at the bottom left of the screen, which looked bizarre)
  • ConfirmationDialog replacing native NSAlert
  • KanodoCalendar custom calendar picker (SwiftUI's DatePicker looked terrible, so I built my own that actually looks good)
  • DashboardWidget for dashboard sections

Building a component library wasn't planned from the start. It emerged naturally as duplication became obvious. When I found myself copying the same 50 lines of form field code for the third time, that was the signal to extract it.

This approach reduced the codebase significantly while improving consistency. Change the TextInput component once, and every form in the app updates. Fix a bug in the confirmation dialog, and all deletion flows benefit. It's basic software engineering, but doing it incrementally as needs arise works better than trying to architect everything upfront.

Technical Challenges and Solutions

The StoreKit Journey

Unlike Chronode, Kanodo doesn't need Accessibility permissions, so it can be sandboxed and distributed through the App Store. I initially implemented StoreKit 1 for in-app purchases, and it works fine.

I'll be migrating to StoreKit 2 before launch for better transaction handling and more modern APIs. The fundamentals won't change much, but the implementation will be cleaner and more maintainable.

Multi-Line Paste Detection

This is a small feature that makes a big difference. When you paste multiple lines of text into the "add card" field, Kanodo detects it and offers three options:

  • Create a single card with all the text
  • Create multiple cards (one per line)
  • Cancel and keep the pasted text

It's inspired by Trello's behaviour and saves considerable time when you're dumping a list of tasks into a column.

Markdown Editor with Image Support

Cards support full markdown editing with live preview. The editor has a comprehensive toolbar with shortcuts for headings, lists, links, images, and formatting.

Images uploaded through the markdown editor are stored separately from regular attachments in an Images directory. They use a custom URL scheme (kanodo://image/) and get properly cleaned up when you remove them from the markdown or delete the card.

The image cleanup service tracks which images are actually referenced in the markdown content and removes orphaned files automatically.

Test Coverage

The app has 630 unit tests covering services, models, and integrations. Tests use in memory Core Data stores for isolation. Services use dependency injection for mockability.

I didn't write tests as I went. The app was about 80% complete before I took the time to add comprehensive test coverage. It would have been smarter to write them alongside the features, but at least they exist now. Writing tests after the fact revealed several edge cases and bugs that would have been annoying to discover in production.

Current Status and What's Left

All the core features are done:

  • Full CRUD for workspaces, boards, columns, cards
  • Multi window card editing
  • Mini boards with drag and drop
  • File attachments with iCloud sync
  • Three date system with urgency indicators
  • Label system with custom colours
  • Global and board scoped search
  • Comprehensive dashboard analytics
  • 630 unit tests passing
  • Over 370 localised strings

The app works. It's stable. I've been using it for my own development work throughout the process.

What Needs to Happen

The app is functionally complete and stable. I've been using it for my own work throughout development. But there's a difference between "works on my machine" and "ready for public release." Before I can publish to the App Store, several important tasks need completion:

StoreKit 2 Migration

Replace the current StoreKit 1 implementation with StoreKit 2 for better transaction handling. Test the complete purchase and restore flows.

UserDefaults Security

Similar to the lesson learned from Audibar and applied to Chronode, I need to ensure licence verification happens on every launch. Local storage can't be trusted for security critical data.

Website and Documentation

Build a proper marketing site that explains what Kanodo actually does and why someone would choose it over Trello or Notion. The site needs to showcase the unique features like windowed cards and mini boards with clear explanations and visuals.

Write comprehensive user documentation covering everything from basic workspace setup to advanced features like iCloud migration and the three date system. Users shouldn't have to figure things out by trial and error.

Marketing Materials:

Create polished screenshots showing the app in action. Multiple workspaces with real looking data. Cards open in windows. The dashboard with actual statistics. Mini boards demonstrating the nested kanban concept.

Prepare the complete App Store listing with descriptions, feature lists, and privacy information. Write compelling copy that explains the value proposition clearly without overselling. The screenshots need to tell the story at a glance.

Final Testing

A couple of rounds of thorough manual testing to catch any last minute bugs before submission. Test every user flow from onboarding to workspace deletion. Verify drag and drop works reliably across all contexts. Ensure file attachments upload, display, and delete properly. Test the purchase flow and licence restoration.

Check that iCloud sync works correctly. Make sure nothing crashes when you do unexpected things. The unit tests cover a lot, but they can't catch everything. Real usage testing matters.

None of this is technically difficult. It's just work that needs doing.

Timeline

I started Kanodo in July 2025 while still working full time. I'd put in a few hours here and there, weekends mostly. I worked on it through August and into September before pausing to build Chronode.

It's been sitting dormant for a couple of months while I worked on the other two apps. But it will be published to the App Store by the end of 2025. That's the target.

What I Learned

Swift Becomes Natural

The initial learning curve was steep, but Swift feels natural now. The type system that seemed restrictive at first actually prevents entire categories of bugs. Optionals force you to handle nil cases explicitly. The compiler catches mistakes before they become runtime errors.

SwiftUI's declarative approach took time to internalise, but it's genuinely a better way to build interfaces once you understand it. State flows through the view hierarchy predictably. Updates happen automatically when data changes.

Scope Grows Naturally

Kanodo started as a simple idea. Basic kanban board. Offline storage. Clean interface. That's it. Then I learned about Core Data relationships and thought, what if cards could contain mini boards? Then I discovered how to build custom colour pickers and realised labels should have user defined colours. Then I learned about NSWorkspace and thought, multiple card windows would be amazing.

Each new capability opened possibilities. The more I learned, the more I implemented. The app grew from minimal to feature rich organically, not because I planned it that way, but because I kept discovering what was possible.

Testing Matters Eventually

I should have written tests alongside features. I know this. Everyone knows this. But I didn't. Writing 630 tests after the fact was tedious. It revealed bugs I'd been living with. It gave me confidence to refactor without breaking things. It made the codebase more maintainable.

The lesson isn't that you should write tests. Everyone already tells you that. The lesson is that you'll eventually write them anyway when the codebase gets complex enough, so you might as well do it earlier when it's easier.

Native Apps Are Better

Working on Kanodo reinforced why native apps matter. The multi window system works because I'm using AppKit's window management. The file storage integrates with iCloud Drive properly. The drag and drop uses native APIs. The whole app feels Mac native because it is.

Web apps can approximate this, but they're always working against the platform. Native apps work with it.

Would I Do It Again?

Honestly? Yes. Originally Kanodo was intended to be minimal and simple. Just enough to be useful. But the more I learned, the more I implemented. Ideas kept emerging.

Things like the workspace dashboards could have been simple lists. Instead, I built beautiful reporting interfaces with comprehensive analytics. The mini board system could have been just checklists. Instead, I built nested kanban boards.

I went whole hog on features because I was enjoying the process of building and learning. If I'd known upfront how much work it would become, I might have been more cautious. But I'm glad I wasn't.

The Development Experience

Solo Development is Flexible

Building alone means you make all the decisions. No meetings about whether the colour picker should use RGB or HSB. No discussions about whether mini boards are a good idea. You just build what makes sense and iterate when it doesn't. The flip side is you're responsible for everything. Documentation, testing, marketing, support. All of it.

Learning by Building Works

I learned Swift by building a real project with genuine complexity. Not tutorials. Not sample apps. An actual kanban board with all the messy requirements that real software has.

This approach has drawbacks. I made mistakes I could have avoided with more structured learning. I probably wrote terrible code in places. But I also built something substantial while learning, which feels more productive than working through exercises.

The Pause was Productive

Stepping away from Kanodo to build Chronode and Audibar wasn't wasted time. I learned things on those projects that I can bring back. The StoreKit experience. The licensing system architecture. The release process automation. When I return to finish Kanodo, I'll be better equipped to ship it properly.

What's Next

Finishing and Shipping

The work remaining is all deployment related, not feature work. Migrate to StoreKit 2. Build the website. Create marketing materials. Test thoroughly. Submit to the App Store. These tasks aren't technically challenging. They're just work that needs doing methodically.

Originally for Me, Now for Everyone

I built Kanodo to solve my own problem. I needed a native macOS kanban app that worked offline with proper file management and flexibility.

But the more I worked on it, the more I realised others would benefit from it too. The multi window card system is genuinely useful. The mini boards solve real workflow problems. The privacy and offline first approach matters to people. It started as a personal tool. It became something worth publishing.

The End of 2025

Kanodo will be on the App Store by the end of 2025. That's the commitment. I've built three macOS apps this year while learning an entirely new platform. Finishing one of them properly seems like a reasonable goal. Plus, I want to actually use it without having to rebuild it from source every time macOS updates.

Final Thoughts

On Learning Swift

Coming from Laravel to Swift as my first macOS development experience was simultaneously harder and easier than expected. Harder because the entire ecosystem was unfamiliar. Easier because solid engineering principles transfer across languages.

I don't regret jumping in with a complex project. The learning curve was steep, but I emerged with a functional app and genuine understanding of SwiftUI, Core Data, and macOS development patterns.

On Building in Public

I didn't build Kanodo "in public" in the social media sense. No progress tweets. No build logs shared publicly. Just documentation for myself and whoever helps with development.

This let me make mistakes privately, restart when needed, and change direction without explaining myself. There's value in working quietly until you have something worth showing.

On Feature Creep

Kanodo has significantly more features than initially planned. Some might call this scope creep. I call it organic growth driven by learning and possibility.

Every feature exists because I discovered how to build it and thought it would be useful. The app is better for the expansion, even if it took longer to build.

On First Projects

Your first project in a new language will be messy. You'll write code you later want to rewrite. You'll make architectural decisions you don't fully understand yet. That's fine. Better to build something real with imperfect code than build nothing with perfect theoretical knowledge.

Kanodo is my first Swift project. It's not perfect. But it works, it's useful, and it taught me what I needed to know to build the next two apps more efficiently. Sometimes you just need to start building and figure it out as you go.


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!