Skip to content

Latest commit

 

History

History
464 lines (365 loc) · 9.88 KB

File metadata and controls

464 lines (365 loc) · 9.88 KB

DrumFlow Architecture

Overview

DrumFlow is a single-page React application built with a component-based architecture, using React Hooks for state management and localStorage for data persistence.


Architecture Patterns

State Management

  • Centralized State: All global state lives in App.js
  • Props Down, Callbacks Up: Data flows down via props, events bubble up via callbacks
  • Custom Hooks: Business logic encapsulated in reusable hooks
  • Local Storage: Automatic persistence via useEffect

Component Hierarchy

App.js (State Container)
├── Header (Navigation + Stats)
├── HomePage (Dashboard)
├── LibraryPage (Lessons Browser)
├── SongsPage (Song Progression)
└── SessionFlow (Practice Session)

Data Flow

State Flow Diagram

┌─────────────────────────────────────────┐
│           App.js (State)                │
│  ┌──────────────────────────────────┐   │
│  │ songs, pinnedItems, excludedItems│   │
│  │ stats, sessionType, currentScreen│   │
│  └──────────────────────────────────┘   │
└────────┬────────────────────────────────┘
         │
         ├──> Header (read stats)
         │
         ├──> HomePage (read routine, trigger actions)
         │
         ├──> LibraryPage (read/write pins & excludes)
         │
         ├──> SongsPage (read/write songs)
         │
         └──> SessionFlow (read routine, trigger completion)

localStorage Keys

Key Description Structure
drumflow_songs Song progression & status Array of song objects
drumflow_pinned Pinned lesson IDs Array of strings
drumflow_excluded Excluded lesson IDs Array of strings
drumflow_stats User statistics { streak, sessions, lastPracticeDate }
drumflow_todays_routine Today's generated routine { date, routine: { warmup, groove, song } }

Key Components

App.js (State Container)

Responsibilities:

  • Global state management
  • localStorage persistence
  • Screen navigation routing
  • Event handling orchestration

State:

{
  currentScreen: 'home' | 'library' | 'songs' | 'session',
  songs: Song[],
  pinnedItems: string[],
  excludedItems: string[],
  stats: { streak, sessions, lastPracticeDate },
  sessionType: '20min' | '10min',
  sessionStep: number,
  isInSession: boolean
}

Header Component

Props:

{
  currentScreen: string,
  onNavigate: (screen) => void,
  stats: StatsObject,
  songs: Song[]
}

Responsibilities:

  • Navigation tabs
  • Display user stats (streak, sessions, songs)
  • Highlight active screen

HomePage Component

Props:

{
  routine: RoutineObject,
  onGenerateRoutine: () => void,
  onStartSession: (type) => void,
  songs: Song[],
  onMarkComplete: (songId) => void
}

Features:

  • Session start buttons (20min, 10min)
  • Today's routine card
  • Regenerate routine
  • Current & goal song display
  • Mark song complete

LibraryPage Component

Props:

{
  onBack: () => void,
  pinnedItems: string[],
  excludedItems: string[],
  onTogglePin: (id) => void,
  onToggleExclude: (id) => void
}

Features:

  • 3 tabs: Warmups, Grooves & Skills, Practice Lessons
  • Pin/Exclude functionality
  • Video links to YouTube
  • Filter by lesson type

SongsPage Component

Props:

{
  onBack: () => void,
  songs: Song[],
  onMarkComplete: (songId) => void,
  onToggleHidden: (songId) => void
}

Features:

  • Search songs by title/difficulty
  • Toggle show/hide hidden songs
  • Sort by level or difficulty
  • Mark current song complete
  • Hide/show songs
  • Links to tutorials, play-alongs, sheet music

SessionFlow Component

Props:

{
  sessionType: '20min' | '10min',
  currentStep: number,
  routine: RoutineObject,
  onNext: () => void,
  onExit: () => void
}

Features:

  • Step-by-step practice flow
  • Progress circle indicator
  • Video/song links for each step
  • Continue/Complete buttons
  • Exit to home

Custom Hooks

useRoutineGenerator

Location: src/hooks/useRoutineGenerator.js

Purpose: Generate daily practice routines with smart rotation

Algorithm:

IF no pinned items:
  Rotate through all non-excluded items (day-of-year % available.length)
ELSE IF 1 pinned item:
  Always use that item
ELSE IF 2+ pinned items:
  Rotate only through pinned items (day-of-year % pinned.length)

Returns:

{
  routine: { warmup, groove, song },
  generateRoutine: () => void
}

Dependencies:

  • Regenerates when pinnedItems or excludedItems change
  • Saves to localStorage with today's date

Data Models

Song Object

{
  id: string,
  level: number,
  title: string,
  difficulty: string,
  tutorialUrl: string,
  playalongUrl: string,
  sheetMusic: string,
  tempo: string,
  status: 'planned' | 'current' | 'completed' | 'goal',
  hidden?: boolean
}

Lesson Object

{
  id: string,
  title: string,
  url: string,
  duration: string,
  level?: string,
  description: string,
  lessonType: 'warmup' | 'groove' | 'practice'
}

Routine Object

{
  warmup: Lesson,
  groove: Lesson,
  song: Song
}

Stats Object

{
  streak: number,
  sessions: number,
  lastPracticeDate: string | null
}

Key User Flows

Starting a Practice Session

1. User clicks "Start Your Session (20min)" on HomePage
2. App.js sets sessionType='20min', isInSession=true, currentScreen='session'
3. SessionFlow renders with routine from useRoutineGenerator
4. User clicks through steps (warmup → groove → song)
5. On "Complete Session", App.js updates stats and returns to home

Marking a Song Complete

1. User clicks "Mark Complete" on HomePage
2. App.js calls handleMarkSongComplete(songId)
3. Song status changes to 'completed'
4. Next non-hidden, non-goal song becomes 'current'
5. State persists to localStorage
6. UI updates to show new current song

Pinning a Lesson

1. User clicks Pin icon in LibraryPage
2. LibraryPage calls onTogglePin(lessonId)
3. App.js updates pinnedItems array
4. useRoutineGenerator detects change
5. New routine generated respecting pins
6. HomePage shows updated routine

Design Decisions

Why localStorage Instead of Database?

Pros:

  • Zero backend complexity
  • Works offline
  • Fast and responsive
  • Private by default
  • No hosting costs

Cons:

  • Data lost if browser cache cleared
  • No cross-device sync
  • Limited to ~10MB storage

Decision: localStorage is perfect for MVP and solo users. Future versions can add optional cloud sync.

Why No React Router?

Reasoning:

  • Simple state-based navigation is sufficient
  • Fewer dependencies
  • No URL management needed (single-page feel)
  • Easy to reason about

Trade-off: URLs don't reflect current screen. Acceptable for this use case.

Why Custom Hook for Routine Generation?

Benefits:

  • Separates business logic from UI
  • Reusable if needed elsewhere
  • Easy to test
  • Encapsulates localStorage logic

Why Centralized State in App.js?

Benefits:

  • Single source of truth
  • Easy to debug
  • Clear data flow
  • Simple persistence strategy

Alternative Considered: Context API - overkill for this scale


Testing Strategy

Manual Testing Checklist

  • Routine generates correctly
  • Pins/excludes affect routine
  • Sessions complete successfully
  • Streak increments properly
  • Song progression works
  • localStorage persists data
  • All external links work

Future Automated Testing

  • Unit tests for useRoutineGenerator
  • Integration tests for state management
  • E2E tests for critical user flows

Performance Considerations

Optimization Strategies

  • Minimal re-renders (proper React.memo usage if needed)
  • localStorage reads/writes batched in useEffect
  • No expensive computations in render

Bundle Size

  • Tailwind CSS via CDN (no build needed)
  • Lucide React icons (tree-shakeable)
  • No heavy dependencies

Security & Privacy

  • No authentication: Not needed for MVP
  • Local-only data: Nothing sent to servers
  • External links: All open in new tabs with rel="noopener noreferrer"
  • No tracking: No analytics or user tracking

Future Architecture Considerations

Scalability

  • Add React Context for deeply nested props
  • Implement React Router if multi-page needed
  • Consider Redux if state becomes complex

Features Requiring Architecture Changes

  • User accounts: Need backend + authentication
  • Cloud sync: Need database + API
  • Real-time features: Need WebSockets
  • Mobile app: Need React Native refactor

Development Guidelines

Component Structure

// 1. Imports
import React from 'react';
import { Icon } from 'lucide-react';

// 2. Component function with JSDoc
/**
 * ComponentName description
 * @param {Object} props - Props description
 */
const ComponentName = ({ prop1, prop2 }) => {
  // 3. State and hooks
  const [state, setState] = useState(initial);
  
  // 4. Event handlers
  const handleEvent = () => {
    // logic
  };
  
  // 5. Render
  return (
    <div>
      {/* JSX */}
    </div>
  );
};

// 6. Export
export default ComponentName;

Naming Conventions

  • Components: PascalCase (HomePage.jsx)
  • Hooks: camelCase with 'use' prefix (useRoutineGenerator.js)
  • Functions: camelCase (handleStartSession)
  • Constants: UPPER_SNAKE_CASE (MAX_SESSIONS)
  • Files: Match component name

This architecture document should be updated as the system evolves.