Building Catspense - From Idea to Architecture
One day after my first blog post, I'm already starting the next project. This is Catspense - an open-source expense tracker I'm building with community contribution in mind.
Not just another expense tracker
There are plenty of expense trackers. Here's what I'm doing differently:
Plugin-first architecture. Instead of trying to support every bank myself, I'm building a system where developers can create email parser plugins for their own banks. Parsers for Jenius, Mandiri, Wise, and eventually banks from other countries - all community-contributed.
Built for open source. This isn't just for me. I want it to be a platform where developers can contribute parsers and benefit from each other's work.
International by design. Starting with Indonesian banks because that's what I use, but architected to handle any currency and banking system.
Why "Catspense"?
Cat + Expense. Memorable enough, not too serious. The cat theme stays subtle.
Tech Stack Decisions
Here's what I've settled on and why.
Monorepo Architecture
- pnpm workspaces for package management
- Turborepo for build orchestration
- Clean separation between apps, packages, and plugins
Frontend Stack
- Next.js 15 with App Router for the web app
- Tamagui for the design system (planning mobile later)
- TypeScript throughout
- Tailwind CSS for utility-first styling
Backend & Data
- tRPC for type-safe APIs
- Supabase for database and auth
- Zod for validation schemas
The Plugin System
This is the part that matters most. Here's how it works:
interface EmailParser {
name: string;
version: string;
parseEmail: (emailContent: string) => Transaction[];
validateEmail: (emailContent: string) => boolean;
}
Each plugin is a self-contained package that knows how to:
- Identify if an email is from their target bank
- Extract transaction data from that email
- Return standardized transaction objects
Planned Official Plugins
- @catspense/plugin-jenius - Jenius SMBC email parser (my primary bank)
- @catspense/plugin-mandiri - Mandiri bank emails (my secondary bank)
- @catspense/plugin-template - Boilerplate for new parsers
I'm starting with these two because they're the banks I actually use. Once the system works with real data, I'll open source it and let others add parsers for their own banks.
Repository Structure
catspense/
├── apps/
│ ├── web/ # Next.js web app
│ └── mobile/ # React Native (future)
├── packages/
│ ├── ui/ # Tamagui design system
│ ├── shared/ # Business logic
│ ├── database/ # Supabase schemas
│ ├── plugin-system/ # Core plugin architecture
│ ├── plugins/ # Official email parsers
│ └── config/ # Shared configurations
├── docs/ # Documentation
└── [standard files]
Multi-Currency Support
Since this is meant for international use, multi-currency is built into the foundation:
- IDR (Indonesian Rupiah) - my primary currency
- USD, SGD, MYR - common regional currencies
- Extensible system for adding more
- Proper formatting and display for each currency
What's Next?
Today I'm focusing on:
- Setting up the monorepo structure - Getting pnpm workspaces and Turborepo configured
- Building the plugin system foundation - Core interfaces and registration system
- Creating the design system - Tamagui setup with the Zen-inspired theme
- Initial web app scaffold - Basic Next.js setup with routing
Tomorrow:
- First email parser plugin (probably Jenius since that's my main bank)
- Transaction dashboard UI
- Plugin management interface
Tech Stack Deep Dive
Here's the reasoning behind each choice.
Monorepo with pnpm + Turborepo
Why monorepo? With a plugin architecture, you need to manage multiple related packages that share dependencies and types. A monorepo keeps everything in sync.
Why pnpm over npm/yarn? Faster installs, better disk space efficiency, stricter dependency resolution. For an open source project, fast setup matters.
Why Turborepo? Incremental builds and smart caching. When the core plugin system changes, only affected packages rebuild.
Next.js 15 with App Router
Why Next.js? Server-side rendering for better SEO matters for an open source project's discoverability. The App Router gives a clean way to organize the dashboard, plugin management, and docs pages.
Why not Vite/React SPA? Marketing pages and docs need to load fast and be SEO-friendly. tRPC also integrates well with Next.js API routes.
Tamagui for Design System
Why Tamagui over Tailwind components? I'm planning mobile later. Tamagui gives React Native compatibility out of the box, so components can be shared between web and mobile.
Why not just Tailwind? Tailwind is great for styling, but I want a more structured design system with consistent theming and built-in dark mode. Tamagui gives that.
tRPC + Supabase
Why tRPC? End-to-end type safety from database to frontend. When a schema changes, TypeScript catches breaking changes across the stack.
Why Supabase over a custom backend? I want to focus on the plugin system, not building auth and database infrastructure from scratch. Supabase gives me PostgreSQL, real-time subscriptions, and auth without that overhead.
Why not Prisma + custom backend? Speed to MVP. Supabase's auto-generated types and real-time features let me build faster and focus on what actually differentiates Catspense.
TypeScript Everywhere
With a plugin system, type safety is critical. Plugin developers need clear interfaces, and the core system needs confidence that plugins won't break things.
Open Source Strategy
The repository is private while I build the initial MVP. But everything is designed for open source from day one - architecture, docs structure, plugin system.
Once I have a working proof of concept with my two bank parsers functioning reliably, I'll open source the whole project with:
- Clear contribution guidelines
- Plugin development documentation
- Issue templates for bug reports and feature requests
- Community guidelines
Why wait for MVP? I want contributors to see a working system they can build on, not empty folders and TODO comments. First impressions matter in open source.
Why this approach
Building Catspense as a plugin-based open source system means:
- Collective value: Every parser someone contributes helps users everywhere
- Local expertise: Developers who use specific banks can build the best parsers for them
- Shared maintenance: The community can maintain and improve parsers over time
- Good learning project: Monorepo architecture, plugin systems, open source maintainership
Following Along
I'll document everything as I build - what works, what doesn't, and the inevitable configuration problems. The repository will be public once the core plugin system works and at least one parser is functional.
Next post will probably be about the monorepo setup and getting the first plugin working. Or about the configuration headaches that come with it.
Post #2 in my development journey. GitHub link coming once the POC is ready.