Skip to main content

Decision log

This document tracks major decisions made during development, why they were chosen, and alternatives considered.

Structure

  • Date — When the decision was made
  • Decision — What was chosen
  • Reasoning — Why this option was selected
  • Alternatives — Options not taken (and why)
  • Impact — What this enables or constrains

Decision history

note

Expand each decision for more details.

September 2025

Lock Cursor AI to Claude-4 Sonnet
  • Decision: Lock Cursor AI to Claude-4 Sonnet instead of Auto
  • Reasoning: Auto can switch models mid-session, risking code inconsistency and project breakage. Sonnet produces stable outputs and auto-generates to-do lists
  • Alternatives: Auto (too risky), cheaper models (less capable for complex flows)
  • Impact: Stable, predictable code generation with reduced risk of silent breakage
Database-First Token Architecture
  • Decision: Store Strava tokens in PostgreSQL instead of file-based storage
  • Reasoning: Database storage provides better cross-device access, audit trails, concurrent access, and backup integration
  • Alternatives: File-based tokens (cross-device issues), cookies only (limited storage), Redis (adds complexity)
  • Impact: Enables cross-device authentication and audit trails, removes file sync issues
Real-Time Webhooks Over Polling
  • Decision: Implement Strava webhooks for real-time activity sync instead of relying solely on polling
  • Reasoning: Webhooks provide ~30-second latency vs ~4-hour polling, better UX, and ~95% fewer API calls
  • Alternatives: Polling only (slow, high API usage), hybrid approach (added complexity)
  • Impact: Near real-time sync, significantly reduced API usage, better user experience
Normalised Split Data Storage
  • Decision: Extend Split model with real distance and duration fields instead of parsing raw JSON
  • Reasoning: Shows accurate partial distances like “0.08 km” instead of assumed “1.00 km”, improves performance, enables structured queries
  • Alternatives: Parse raw JSON on demand (slower, less accurate), fake 1 km assumptions (inaccurate)
  • Impact: Accurate split display, better database performance, enables advanced analytics
Lazy Loading for Thumbnails
  • Decision: Implement lazy loading with the Intersection Observer API instead of eager loading all thumbnails
  • Reasoning: Reduced initial HTTP requests by ~95% (from 40+ to 1), faster page loads, better mobile performance
  • Alternatives: Eager loading (performance issues), no thumbnails (poor UX), server-side rendering (complexity)
  • Impact: Significantly improved page load performance, especially on mobile
Enterprise-Grade Audit Trails
  • Decision: Implement soft deletion with isActive flags instead of hard deletion for tokens and webhooks
  • Reasoning: Preserves complete audit history for security, compliance, and debugging
  • Alternatives: Hard deletion (data loss), separate audit tables (complexity), no audit trail (compliance risk)
  • Impact: Complete authentication history, better security auditing, compliance-ready architecture
Webhook Subscription Caching
  • Decision: Implement database-backed caching with 5-minute TTL for webhook subscription management
  • Reasoning: Reduces Strava API calls by ~95%, provides instant UI response (10–50 ms vs 500–1000 ms)
  • Alternatives: Always hit Strava API (slow, rate limited), no caching (poor UX), Redis cache (adds dependency)
  • Impact: Fast webhook management UI, reduced API usage, improved user experience
Single Comprehensive Admin System
  • Decision: Build an integrated admin system with seven endpoints covering webhooks, jobs, thumbnails, and analytics
  • Reasoning: Provides complete observability and control in one place, easier monitoring and troubleshooting
  • Alternatives: Separate admin tools (fragmented), no admin system (poor ops), third-party tools (vendor lock-in)
  • Impact: Complete system observability, easier operations, self-contained monitoring
Mobile-First UI Design
  • Decision: Design specifically for Safari on iPhone 14 Pro Max with clean mobile aesthetics
  • Reasoning: Primary use case is mobile; modern mobile apps offer excellent UX patterns; dark theme reduces eye strain
  • Alternatives: Desktop-first (poor mobile UX), generic responsive (not optimised), other styles (less familiar)
  • Impact: Excellent mobile experience, familiar UX patterns, optimised for the primary use case
Australian Date/Time Formatting
  • Decision: Use Australian date format with 24-hour time: “Wednesday, 17 September 2025 at 19:04”
  • Reasoning: Matches locale and preferences, avoids AM/PM confusion
  • Alternatives: US format (month/day), 12-hour time (less clear), ISO format (too technical for UI)
  • Impact: Clearer time display and better user experience
ngrok for Development Webhooks
  • Decision: Use ngrok with a custom domain for webhook development and testing
  • Reasoning: Enables local webhook testing, provides a stable HTTPS endpoint, supports custom domains for consistency
  • Alternatives: Test only in production (slow cycle), localhost (no HTTPS), other tunnelling tools (fewer features)
  • Impact: Faster development cycle, reliable webhook testing, consistent dev environment
Job Queue with Exponential Backoff
  • Decision: Implement a database-backed job queue with retry logic and exponential backoff for webhook processing
  • Reasoning: Handles Strava API rate limits gracefully, ensures no lost events, provides an audit trail of processing
  • Alternatives: Synchronous processing (fails on rate limits), Redis queue (adds dependency), no retry (data loss)
  • Impact: Reliable event processing, graceful handling of API limits, complete processing audit trail
Comprehensive Testing Framework and GitLab CI/CD
  • Decision: Implement enterprise-grade testing with Jest and React Testing Library plus a fully automated GitLab CI/CD pipeline
  • Reasoning: Foundation Phase 1 needs production-ready quality assurance, automated testing, and CI/CD for reliable development at scale
  • Alternatives: Manual testing only (error-prone), GitHub Actions (vendor lock-in), simpler CI (insufficient quality gates)
  • Impact: Zero-defect workflow target, automated quality enforcement, production-ready deployments

Testing Framework Details

  • Jest 30.1.3 with Next.js integration and coverage reporting
  • React Testing Library 16.3.0 for component testing best practices
  • 15 tests across three suites (components, utilities, database)
  • Coverage artefacts for GitLab CI integration (JUnit, Cobertura, LCOV)
  • Custom Jest matchers (toBeValidDate) with TypeScript integration

GitLab CI/CD Pipeline Details

  • Five-stage automated pipeline: .pre, validate, test, quality, security
  • 13 jobs with smart rules and parallel processing
  • Node.js 20 on Debian for native module compatibility
  • PostgreSQL test database for realistic integration tests
  • Quality gates: TypeScript, Prettier, ESLint, Jest, docs sync, dependency audit, security scan

Technology Compatibility Decisions

  • TailwindCSS version
    • Decision: Use TailwindCSS v3.4.16 instead of v4.x for Foundation
    • Reasoning: v4 requires lightningcss native binaries that complicate CI
    • Trade-off: Defer some performance improvements in favour of CI stability
    • Future path: Document v4 migration for post-Foundation
  • GitLab CI environment
    • Decision: Use node:20 (Debian) instead of node:20-alpine
    • Reasoning: Better native binary support (glibc vs musl)
    • Impact: Resolves lightningcss and other native module compilation issues