process

Building TimeBlocker Part 1: Documentation-First Development

4 min read

TimeBlocker - Cross-platform time management app

I’m starting a new side project - a time-blocking app called TimeBlocker. But before writing any code, I spent the past few days doing something that might seem counterproductive: writing documentation.

12 documents. Zero lines of implementation code. Here’s why.

The Problem with “Just Start Coding”

I’ve started plenty of projects by jumping straight into code. Scaffold the app, get something on screen, figure out the rest as you go. It works for small things. For anything complex? You end up rewriting the same code three times because you didn’t think through the data model, or you realize halfway through that your architecture doesn’t support a core feature.

This time I wanted to try something different.

Documentation First

Before touching any code, I wrote out everything:

docs/
├── 01-project-overview.md
├── 02-functional-requirements.md
├── 03-technical-architecture.md
├── 04-convex-schema.md
├── 05-auth0-integration.md
├── 06-ui-ux-specifications.md
├── 07-menu-bar-design.md
├── 08-offline-sync-strategy.md
├── 09-calendar-integration.md
├── 10-development-setup.md
├── 11-ci-cd-pipeline.md
└── 12-implementation-plan.md

Each document forces you to think through decisions before you’re knee-deep in code. The functional requirements doc made me realize I needed both fixed-time blocks (9:00-10:00) AND duration-based blocks (45 minutes). The offline sync strategy doc revealed edge cases I would’ve hit weeks into development.

The implementation plan breaks everything into 20 phases. Not because I’ll follow it perfectly, but because it surfaces dependencies. You can’t build the timer UI before the Convex schema exists. You can’t add calendar sync before auth works.

Specialized AI Agents

Here’s where it gets interesting. I’m using Claude Code for this project, and I set up specialized agents for different parts of the stack:

.claude/agents/
├── convex-backend.md      # Database, mutations, sync
├── tauri-desktop.md       # Menu bar, Rust integration
├── sveltekit-web.md       # Web dashboard
├── expo-ios.md            # iOS app, Live Activities
├── ui-ux.md               # Design system, components
├── devops.md              # CI/CD, deployments
└── distinguished-engineer.md  # Code review

Each agent has the project context baked in. The Expo agent knows we’re iOS-only - it won’t suggest Android code. The UI agent enforces dark-first design. The DevOps agent knows our CI only runs lint and typecheck.

The workflow is simple:

  1. Identify which agent owns the task
  2. Let that agent implement it
  3. Distinguished Engineer reviews before it’s done

The Distinguished Engineer is basically a senior reviewer that checks everything against our architecture docs and project constraints. Nothing ships without approval.

The No-Tests Approach

This might be controversial: I’m not writing unit tests.

Instead, I’m relying on:

The CI pipeline runs pnpm lint and pnpm typecheck. That’s it. No test jobs.

Why? Because for a side project, the ROI on comprehensive tests isn’t there. TypeScript’s type system catches 90% of what unit tests would catch. The other 10%? I’ll find those in actual usage.

This only works because:

  1. Strict TypeScript catches type mismatches
  2. The documentation defines expected behavior
  3. The Distinguished Engineer agent validates against constraints

If I were building this for a team or for production at scale, I’d write tests. For a side project where I’m the only developer? Strict types and good docs are enough.

Conflict Protocol

One thing I added to prevent documentation drift: a conflict protocol.

Before making any change, I check if it conflicts with existing docs. If it does, I have to explicitly validate the change and update the affected documents with a changelog entry.

This keeps the docs as the source of truth. Code follows docs, not the other way around.

What I’ve Learned So Far

Writing docs first feels slow, but it’s actually faster. You make decisions once, in writing, instead of making them repeatedly in code. You catch contradictions (“wait, if it’s offline-first, how does real-time sync work?”) before they become bugs.

The agent setup took a few hours, but now every implementation task has context. I don’t have to re-explain the project constraints every time.

What’s Next

The documentation is done. The agents are configured. Tomorrow I start Phase 1: setting up the monorepo with Turborepo and scaffolding the three apps.

Part 2 will cover the actual implementation kickoff.


Part 1 of the TimeBlocker build series.