mirror of
https://github.com/DocNR/POWR.git
synced 2025-06-05 08:42:05 +00:00
doc and planning update
This commit is contained in:
parent
f26a59f569
commit
4ee041483f
427
docs/design/Social/SocialDesignDocument.md
Normal file
427
docs/design/Social/SocialDesignDocument.md
Normal file
@ -0,0 +1,427 @@
|
||||
# POWR Social Features Design Document
|
||||
|
||||
## Problem Statement
|
||||
POWR needs to integrate social features that leverage the Nostr protocol while maintaining a local-first architecture. The system must provide a seamless way for users to share workout content, receive feedback, and engage with the fitness community without compromising the standalone functionality of the application. Additionally, the implementation must support future integration with value-exchange mechanisms through Nostr Wallet Connect.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
- Custom Nostr event types for exercises, workout templates, and workout records
|
||||
- Social sharing of workout content via NIP-19 references
|
||||
- Comment system on exercises, templates, and workout records
|
||||
- Reactions and likes on shared content
|
||||
- App discovery through NIP-89 handlers
|
||||
- Support for zaps and Lightning payments via NWC
|
||||
- Ability to track template usage and popularity
|
||||
- User profile and content discovery
|
||||
- Local-first storage with Nostr sync capabilities
|
||||
|
||||
### Non-Functional Requirements
|
||||
- Performance: Social content loads within 500ms when online
|
||||
- Security: User private keys are never exposed to the application
|
||||
- Reliability: All created content must be usable offline
|
||||
- Usability: Social interactions should be intuitive and seamlessly integrated
|
||||
- Privacy: Users control what content gets shared publicly
|
||||
- Scalability: System handles thousands of exercise templates and workout records
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### 1. Custom Event Kinds vs. Standard Kinds
|
||||
**Approach**: Use custom event kinds (33401, 33402, 33403) for exercises, templates, and workout records rather than generic kind 1 events.
|
||||
|
||||
**Rationale**:
|
||||
- Custom kinds enable clear data separation and easier filtering
|
||||
- Avoids confusion between social posts and fitness data
|
||||
- Enables specialized app handlers via NIP-89
|
||||
- Aligns with best practices for specialized content types
|
||||
- Enables distinct validation rules for each content type
|
||||
|
||||
**Trade-offs**:
|
||||
- Requires implementing NIP-89 app handlers for client support
|
||||
- Less immediate visibility in generic Nostr clients
|
||||
- Needs additional social sharing mechanism for discovery
|
||||
|
||||
### 2. Template-Record Relationship
|
||||
**Approach**: Implement standalone workout records with explicit references to templates.
|
||||
|
||||
**Rationale**:
|
||||
- Cleaner separation between templates and completed workouts
|
||||
- More flexible for workouts that modify templates
|
||||
- Better support for offline-first usage
|
||||
- Simplifies synchronization logic
|
||||
- Easier to implement privacy controls
|
||||
|
||||
**Trade-offs**:
|
||||
- Requires custom queries to track template usage
|
||||
- Doesn't leverage built-in reply notification systems
|
||||
- Additional relationship management logic needed
|
||||
|
||||
### 3. Comments Implementation
|
||||
**Approach**: Use NIP-22 generic comments system with proper reference structure.
|
||||
|
||||
**Rationale**:
|
||||
- Standardized approach compatible with existing Nostr clients
|
||||
- Supports threaded conversations
|
||||
- Clear distinction between content and comments
|
||||
- Integrates with existing notification systems
|
||||
- Simple to implement using established patterns
|
||||
|
||||
**Trade-offs**:
|
||||
- Requires filtering to display relevant comments
|
||||
- Additional UI components for comment display
|
||||
- Need for moderation tools (client-side filtering)
|
||||
|
||||
### 4. Nostr Wallet Connect Integration
|
||||
**Approach**: Implement NIP-47 Nostr Wallet Connect for Lightning payments and zaps.
|
||||
|
||||
**Rationale**:
|
||||
- Secure payment capabilities without managing private keys
|
||||
- Enables zaps on workout content
|
||||
- Creates opportunities for creator compensation
|
||||
- Integrates with existing Nostr Lightning infrastructure
|
||||
- Future-proofs for monetization features
|
||||
|
||||
**Trade-offs**:
|
||||
- Additional complexity in wallet connection management
|
||||
- Dependency on external wallet implementations
|
||||
- Requires careful error handling for payment flows
|
||||
|
||||
## Technical Design
|
||||
|
||||
### Core Components
|
||||
|
||||
```typescript
|
||||
// Exercise Template Event (Kind 33401)
|
||||
interface ExerciseTemplate extends NostrEvent {
|
||||
kind: 33401;
|
||||
content: string; // Detailed instructions
|
||||
tags: [
|
||||
["d", string], // Unique identifier
|
||||
["title", string], // Exercise name
|
||||
["format", ...string[]], // Data structure parameters
|
||||
["format_units", ...string[]], // Units for parameters
|
||||
["equipment", string], // Equipment type
|
||||
["difficulty"?, string], // Optional skill level
|
||||
["imeta"?, ...string[]], // Optional media metadata
|
||||
["t"?, string][], // Optional hashtags
|
||||
]
|
||||
}
|
||||
|
||||
// Workout Template Event (Kind 33402)
|
||||
interface WorkoutTemplate extends NostrEvent {
|
||||
kind: 33402;
|
||||
content: string; // Workout notes and instructions
|
||||
tags: [
|
||||
["d", string], // Unique identifier
|
||||
["title", string], // Workout name
|
||||
["type", string], // Workout type (strength, circuit, etc.)
|
||||
["exercise", ...string[]][], // Exercise references with parameters
|
||||
["rounds"?, string], // Optional rounds count
|
||||
["duration"?, string], // Optional total duration
|
||||
["interval"?, string], // Optional interval duration
|
||||
["rest_between_rounds"?, string], // Optional rest time
|
||||
["t"?, string][], // Optional hashtags
|
||||
]
|
||||
}
|
||||
|
||||
// Workout Record Event (Kind 33403)
|
||||
interface WorkoutRecord extends NostrEvent {
|
||||
kind: 33403;
|
||||
content: string; // Workout notes
|
||||
tags: [
|
||||
["d", string], // Unique identifier
|
||||
["title", string], // Workout name
|
||||
["type", string], // Workout type
|
||||
["template"?, string], // Optional template reference
|
||||
["exercise", ...string[]][], // Exercises with actual values
|
||||
["start", string], // Start timestamp
|
||||
["end", string], // End timestamp
|
||||
["completed", string], // Completion status
|
||||
["rounds_completed"?, string], // Optional rounds completed
|
||||
["interval"?, string], // Optional interval duration
|
||||
["pr"?, string][], // Optional personal records
|
||||
["t"?, string][], // Optional hashtags
|
||||
]
|
||||
}
|
||||
|
||||
// Comment (Kind 1111)
|
||||
interface WorkoutComment extends NostrEvent {
|
||||
kind: 1111;
|
||||
content: string; // Comment text
|
||||
tags: [
|
||||
// Root reference (exercise, template, or record)
|
||||
["e", string, string, string], // id, relay, pubkey
|
||||
["K", string], // Root kind (33401, 33402, or 33403)
|
||||
["P", string, string], // Root pubkey, relay
|
||||
|
||||
// Parent comment (for replies)
|
||||
["e"?, string, string, string], // id, relay, pubkey
|
||||
["k"?, string], // Parent kind (1111)
|
||||
["p"?, string, string], // Parent pubkey, relay
|
||||
]
|
||||
}
|
||||
|
||||
// App Handler Registration (Kind 31990)
|
||||
interface AppHandler extends NostrEvent {
|
||||
kind: 31990;
|
||||
content: string;
|
||||
tags: [
|
||||
["k", "33401", "exercise-template"],
|
||||
["k", "33402", "workout-template"],
|
||||
["k", "33403", "workout-record"],
|
||||
["web", string], // App URL
|
||||
["name", string], // App name
|
||||
["description", string] // App description
|
||||
]
|
||||
}
|
||||
|
||||
// Nostr Wallet Connection Manager
|
||||
class NWCManager {
|
||||
async connectWallet(nwcURI: string): Promise<{
|
||||
connected: boolean;
|
||||
pubkey?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
async getBalance(): Promise<number>;
|
||||
|
||||
async zapEvent(
|
||||
event: NostrEvent,
|
||||
amount: number,
|
||||
comment?: string
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
preimage?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// Social Service
|
||||
class SocialService {
|
||||
// Share workout on social feeds
|
||||
async shareWorkoutSocially(
|
||||
workout: WorkoutTemplate | WorkoutRecord,
|
||||
message: string
|
||||
): Promise<NostrEvent>;
|
||||
|
||||
// Get comments for content
|
||||
async getComments(
|
||||
eventId: string,
|
||||
rootKind: number
|
||||
): Promise<WorkoutComment[]>;
|
||||
|
||||
// Post comment
|
||||
async postComment(
|
||||
rootEvent: NostrEvent,
|
||||
content: string,
|
||||
parentComment?: WorkoutComment
|
||||
): Promise<WorkoutComment>;
|
||||
|
||||
// Track template usage
|
||||
async getTemplateUsageCount(templateId: string): Promise<number>;
|
||||
|
||||
// React to content
|
||||
async reactToEvent(
|
||||
event: NostrEvent,
|
||||
reaction: "+" | "🔥" | "👍"
|
||||
): Promise<NostrEvent>;
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Points
|
||||
|
||||
#### Nostr Protocol Integration
|
||||
- NDK for Nostr event management and relay communication
|
||||
- NIP-19 for nevent URL encoding/decoding
|
||||
- NIP-22 for comment threading
|
||||
- NIP-25 for reactions and likes
|
||||
- NIP-47 for Nostr Wallet Connect
|
||||
- NIP-57 for zaps
|
||||
- NIP-89 for app handler registration
|
||||
|
||||
#### Application Integration
|
||||
- **Library Screen**:
|
||||
- Displays social metrics on exercise/template cards (usage count, likes, comments)
|
||||
- Filters for trending/popular content
|
||||
- Visual indicators for content source (local, POWR, Nostr)
|
||||
|
||||
- **Detail Screens**:
|
||||
- Shows comments and reactions on exercises, templates, and workout records
|
||||
- Displays creator information with follow option
|
||||
- Presents usage statistics and popularity metrics
|
||||
- Provides zap/tip options for content creators
|
||||
|
||||
- **Profile Screen**:
|
||||
- Displays user's created workouts, templates, and exercises
|
||||
- Shows workout history and statistics visualization
|
||||
- Presents achievements, PRs, and milestone tracking
|
||||
- Includes user's social activity (comments, reactions)
|
||||
- Provides analytics dashboard of workout progress and consistency
|
||||
|
||||
- **Settings Screen**:
|
||||
- Nostr Wallet Connect management
|
||||
- Relay configuration and connection management
|
||||
- Social preferences (public/private sharing defaults)
|
||||
- Notification settings for social interactions
|
||||
- Mute and content filtering options
|
||||
- Profile visibility and privacy controls
|
||||
|
||||
- **Share Sheet**:
|
||||
- Social sharing interface for workout records and achievements
|
||||
- Options for including stats, images, or workout summaries
|
||||
- Relay selection for content publishing
|
||||
- Privacy option to share publicly or to specific relays only
|
||||
|
||||
- **Comment UI**:
|
||||
- Thread-based comment creation and display
|
||||
- Reply functionality with proper nesting
|
||||
- Reaction options with count displays
|
||||
- Comment filtering and sorting options
|
||||
|
||||
#### External Dependencies
|
||||
- SQLite for local storage
|
||||
- NDK (Nostr Development Kit) for Nostr integration
|
||||
- NWC libraries for wallet connectivity
|
||||
- Lightning payment providers
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Core Nostr Event Structure
|
||||
1. Implement custom event kinds (33401, 33402, 33403)
|
||||
2. Create event validation and processing functions
|
||||
3. Build local-first storage with Nostr event structure
|
||||
4. Develop basic event publishing to relays
|
||||
|
||||
### Phase 2: Social Interaction Foundation
|
||||
1. Implement NIP-22 comments system
|
||||
2. Create NIP-25 reactions support
|
||||
3. Build NIP-19 social sharing functions
|
||||
4. Implement NIP-89 app handler registration
|
||||
5. Develop UI components for social interactions
|
||||
|
||||
### Phase 3: Nostr Wallet Connect
|
||||
1. Implement NWC connection management
|
||||
2. Create wallet interface in profile section
|
||||
3. Develop zap functionality for content
|
||||
4. Build UI for Lightning interactions
|
||||
5. Add tipping capability for creators
|
||||
|
||||
### Phase 4: Advanced Social Features
|
||||
1. Implement NIP-51 lists for workout collections
|
||||
2. Create user follows and discoveries
|
||||
3. Develop achievement sharing
|
||||
4. Build coaching and feedback tools
|
||||
5. Add paid content capabilities
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- Event validation and processing tests
|
||||
- Comment threading logic tests
|
||||
- Wallet connection management tests
|
||||
- Relay communication tests
|
||||
- Social share URL generation tests
|
||||
|
||||
### Integration Tests
|
||||
- End-to-end comment flow testing
|
||||
- Reaction and like functionality testing
|
||||
- Template usage tracking tests
|
||||
- Social sharing workflow tests
|
||||
- Zap flow testing
|
||||
- Cross-client compatibility testing
|
||||
|
||||
### User Testing
|
||||
- Usability of social sharing flows
|
||||
- Clarity of comment interfaces
|
||||
- Wallet connection experience
|
||||
- Performance on different devices and connection speeds
|
||||
|
||||
## Observability
|
||||
|
||||
### Logging
|
||||
- Social event publishing attempts and results
|
||||
- Relay connection status
|
||||
- Comment submission success/failure
|
||||
- Wallet connection events
|
||||
- Payment attempts and results
|
||||
|
||||
### Metrics
|
||||
- Template popularity (usage counts)
|
||||
- Comment engagement rates
|
||||
- Social sharing frequency
|
||||
- Zaps received/sent
|
||||
- Relay response times
|
||||
- Offline content creation stats
|
||||
|
||||
## Future Considerations
|
||||
|
||||
### Potential Enhancements
|
||||
- Group fitness challenges with bounties
|
||||
- Subscription model for premium content
|
||||
- Coaching marketplace with Lightning payments
|
||||
- Team workout coordination
|
||||
- Custom fitness community creation
|
||||
- AI-powered workout recommendations based on social data
|
||||
|
||||
### Known Limitations
|
||||
- Reliance on external Lightning wallets
|
||||
- Comment moderation limited to client-side filtering
|
||||
- Content discovery dependent on relay availability
|
||||
- Limited backward compatibility with generic Nostr clients
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Runtime Dependencies
|
||||
- NDK (Nostr Development Kit)
|
||||
- SQLite database
|
||||
- Nostr relay connections
|
||||
- Lightning network (for zaps)
|
||||
- NWC-compatible wallets
|
||||
|
||||
### Development Dependencies
|
||||
- TypeScript
|
||||
- React Native
|
||||
- Expo
|
||||
- Jest for testing
|
||||
- NativeWind for styling
|
||||
|
||||
## Security Considerations
|
||||
- Never store or request user private keys
|
||||
- Secure management of NWC connection secrets
|
||||
- Client-side validation of all incoming events
|
||||
- Content filtering for inappropriate material
|
||||
- User control over content visibility
|
||||
- Protection against spam and abuse
|
||||
|
||||
## Rollout Strategy
|
||||
|
||||
### Development Phase
|
||||
1. Implement custom event kinds and validation
|
||||
2. Create UI components for social interactions
|
||||
3. Develop local-first storage with Nostr sync
|
||||
4. Build and test commenting system
|
||||
5. Implement wallet connection interface
|
||||
6. Add documentation for Nostr integration
|
||||
|
||||
### Beta Testing
|
||||
1. Release to limited test group
|
||||
2. Monitor relay performance and sync issues
|
||||
3. Gather feedback on social interaction flows
|
||||
4. Test cross-client compatibility
|
||||
5. Evaluate Lightning payment reliability
|
||||
|
||||
### Production Deployment
|
||||
1. Deploy app handler registration
|
||||
2. Roll out social features progressively
|
||||
3. Monitor engagement and performance metrics
|
||||
4. Provide guides for social feature usage
|
||||
5. Establish relay connection recommendations
|
||||
6. Create nostr:// URI scheme handlers
|
||||
|
||||
## References
|
||||
- [Nostr NIPs Repository](https://github.com/nostr-protocol/nips)
|
||||
- [NDK Documentation](https://github.com/nostr-dev-kit/ndk)
|
||||
- [POWR Workout NIP Draft](nostr-exercise-nip.md)
|
||||
- [NIP-47 Wallet Connect](https://github.com/nostr-protocol/nips/blob/master/47.md)
|
||||
- [NIP-57 Lightning Zaps](https://github.com/nostr-protocol/nips/blob/master/57.md)
|
||||
- [NIP-89 App Handlers](https://github.com/nostr-protocol/nips/blob/master/89.md)
|
4
docs/design/WorkoutTab/Summary.md
Normal file
4
docs/design/WorkoutTab/Summary.md
Normal file
@ -0,0 +1,4 @@
|
||||
Workout Tab Design Document: A detailed technical specification covering all aspects of the workout feature, including requirements, architecture, database schema, and implementation plan.
|
||||
Workout UI Component Specification: A focused document on the user interface components needed for workout tracking, with detailed component props, behaviors, and interaction patterns.
|
||||
Workout Data Flow Specification: A thorough explanation of how data moves through the system, from template selection through workout tracking to storage and synchronization.
|
||||
Workout Implementation Roadmap: A phased approach to building the feature, with clear milestones, testing strategies, and risk management.
|
848
docs/design/WorkoutTab/WorkoutDataFlowSpec.md
Normal file
848
docs/design/WorkoutTab/WorkoutDataFlowSpec.md
Normal file
@ -0,0 +1,848 @@
|
||||
# POWR Workout Data Flow Specification
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the complete data flow for the workout feature, from initialization through completion and storage. The design prioritizes data integrity, performance, and future extensibility with Nostr integration.
|
||||
|
||||
## Data Flow Diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph Initialization
|
||||
A[Template Selection] -->|Load Template| B[Template-to-Workout Transformation]
|
||||
C[Quick Start] -->|Create Empty| B
|
||||
B -->|Initialize| D[Workout Context]
|
||||
end
|
||||
|
||||
subgraph Active Tracking
|
||||
D -->|Current State| E[UI Components]
|
||||
E -->|User Input| F[Dispatch Actions]
|
||||
F -->|State Updates| D
|
||||
D -->|Timer Events| G[Auto-save]
|
||||
end
|
||||
|
||||
subgraph Persistence
|
||||
G -->|Incremental Writes| H[(SQLite)]
|
||||
I[Workout Completion] -->|Final Write| H
|
||||
J[Manual Save] -->|Checkpoint| H
|
||||
end
|
||||
|
||||
subgraph Analysis
|
||||
H -->|Load History| K[PR Detection]
|
||||
H -->|Aggregate Data| L[Statistics Calculation]
|
||||
K --> M[Achievements]
|
||||
L --> M
|
||||
end
|
||||
|
||||
subgraph Sync
|
||||
H -->|Format Events| N[Nostr Event Creation]
|
||||
N -->|Publish| O[Relays]
|
||||
O -->|Subscribe| P[Other Devices]
|
||||
end
|
||||
```
|
||||
|
||||
## Data Transformation Stages
|
||||
|
||||
### 1. Template to Workout Conversion
|
||||
|
||||
```typescript
|
||||
interface WorkoutTemplateToWorkoutParams {
|
||||
template: Template;
|
||||
workoutSettings?: {
|
||||
skipExercises?: string[];
|
||||
addExercises?: WorkoutExercise[];
|
||||
adjustRestTimes?: boolean;
|
||||
scaleWeights?: number; // Percentage multiplier
|
||||
};
|
||||
}
|
||||
|
||||
function convertTemplateToWorkout(
|
||||
params: WorkoutTemplateToWorkoutParams
|
||||
): Workout {
|
||||
// 1. Deep clone template structure
|
||||
// 2. Apply user customizations
|
||||
// 3. Initialize tracking metadata
|
||||
// 4. Generate unique IDs
|
||||
// 5. Add timestamps
|
||||
}
|
||||
```
|
||||
|
||||
Key operations:
|
||||
- Exercise copies maintain reference to source exercise
|
||||
- Sets are initialized with default values from template
|
||||
- Additional metadata fields added for tracking
|
||||
- Timestamps initialized
|
||||
- IDs generated for all entities
|
||||
|
||||
### 2. Workout State Management
|
||||
|
||||
The central reducer handles all state transitions and ensures data consistency:
|
||||
|
||||
```typescript
|
||||
function workoutReducer(
|
||||
state: WorkoutState,
|
||||
action: WorkoutAction
|
||||
): WorkoutState {
|
||||
switch (action.type) {
|
||||
case 'START_WORKOUT':
|
||||
return {
|
||||
...state,
|
||||
status: 'active',
|
||||
activeWorkout: action.payload,
|
||||
startTime: Date.now(),
|
||||
elapsedTime: 0,
|
||||
};
|
||||
|
||||
case 'UPDATE_SET':
|
||||
const { exerciseIndex, setIndex, data } = action.payload;
|
||||
const updatedExercises = [...state.activeWorkout.exercises];
|
||||
const updatedSets = [...updatedExercises[exerciseIndex].sets];
|
||||
|
||||
updatedSets[setIndex] = {
|
||||
...updatedSets[setIndex],
|
||||
...data,
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
|
||||
updatedExercises[exerciseIndex] = {
|
||||
...updatedExercises[exerciseIndex],
|
||||
sets: updatedSets,
|
||||
};
|
||||
|
||||
return {
|
||||
...state,
|
||||
activeWorkout: {
|
||||
...state.activeWorkout,
|
||||
exercises: updatedExercises,
|
||||
lastUpdated: Date.now(),
|
||||
},
|
||||
needsSave: true,
|
||||
};
|
||||
|
||||
// Additional cases for all actions...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Persistence Layer
|
||||
|
||||
Data is saved incrementally with different strategies:
|
||||
|
||||
```typescript
|
||||
class WorkoutPersistence {
|
||||
// Save entire workout
|
||||
async saveWorkout(workout: Workout): Promise<void> {
|
||||
return this.db.withTransactionAsync(async () => {
|
||||
// 1. Save workout metadata
|
||||
// 2. Save all exercises
|
||||
// 3. Save all sets
|
||||
// 4. Update related statistics
|
||||
});
|
||||
}
|
||||
|
||||
// Save only modified data
|
||||
async saveIncrementalChanges(workout: Workout): Promise<void> {
|
||||
const dirtyExercises = workout.exercises.filter(e => e.isDirty);
|
||||
|
||||
return this.db.withTransactionAsync(async () => {
|
||||
// Only update changed exercises and sets
|
||||
for (const exercise of dirtyExercises) {
|
||||
// Update exercise
|
||||
// Update dirty sets
|
||||
exercise.isDirty = false;
|
||||
for (const set of exercise.sets) {
|
||||
set.isDirty = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Save triggers:
|
||||
1. **Auto-save**: Every 30 seconds during active workout
|
||||
2. **Exercise change**: When navigating between exercises
|
||||
3. **Pause**: When workout is paused
|
||||
4. **Completion**: Final save with additional metadata
|
||||
5. **Manual save**: User-triggered save
|
||||
6. **App background**: When app moves to background
|
||||
|
||||
### 4. Workout Completion Processing
|
||||
|
||||
```typescript
|
||||
async function processWorkoutCompletion(workout: Workout): Promise<WorkoutSummary> {
|
||||
// 1. Mark workout as completed
|
||||
const completedWorkout = {
|
||||
...workout,
|
||||
isCompleted: true,
|
||||
endTime: Date.now(),
|
||||
};
|
||||
|
||||
// 2. Calculate final statistics
|
||||
const stats = calculateWorkoutStatistics(completedWorkout);
|
||||
|
||||
// 3. Detect personal records
|
||||
const personalRecords = detectPersonalRecords(completedWorkout);
|
||||
|
||||
// 4. Save everything to database
|
||||
await workoutPersistence.saveCompletedWorkout(
|
||||
completedWorkout,
|
||||
stats,
|
||||
personalRecords
|
||||
);
|
||||
|
||||
// 5. Return summary data
|
||||
return {
|
||||
workout: completedWorkout,
|
||||
statistics: stats,
|
||||
achievements: {
|
||||
personalRecords,
|
||||
streaks: detectStreaks(completedWorkout),
|
||||
milestones: detectMilestones(completedWorkout),
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Nostr Event Creation
|
||||
|
||||
```typescript
|
||||
function createNostrWorkoutEvent(workout: CompletedWorkout): NostrEvent {
|
||||
return {
|
||||
kind: 33403, // Workout Record
|
||||
content: workout.notes || '',
|
||||
tags: [
|
||||
['d', workout.id],
|
||||
['title', workout.title],
|
||||
['type', workout.type],
|
||||
['start', workout.startTime.toString()],
|
||||
['end', workout.endTime.toString()],
|
||||
['completed', workout.isCompleted.toString()],
|
||||
|
||||
// Exercise data
|
||||
...workout.exercises.flatMap(exercise => {
|
||||
const exerciseRef = `33401:${exercise.author || 'local'}:${exercise.sourceId}`;
|
||||
|
||||
return exercise.sets.map(set => [
|
||||
'exercise',
|
||||
exerciseRef,
|
||||
set.weight?.toString() || '',
|
||||
set.reps?.toString() || '',
|
||||
set.rpe?.toString() || '',
|
||||
set.type,
|
||||
]);
|
||||
}),
|
||||
|
||||
// PR tags if applicable
|
||||
...workout.personalRecords.map(pr => [
|
||||
'pr',
|
||||
`${pr.exerciseId},${pr.metric},${pr.value}`
|
||||
]),
|
||||
|
||||
// Categorization tags
|
||||
...workout.tags.map(tag => ['t', tag])
|
||||
],
|
||||
created_at: Math.floor(workout.endTime / 1000),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Data Structures
|
||||
|
||||
### Workout State
|
||||
|
||||
```typescript
|
||||
interface WorkoutState {
|
||||
status: 'idle' | 'active' | 'paused' | 'completed';
|
||||
activeWorkout: Workout | null;
|
||||
currentExerciseIndex: number;
|
||||
currentSetIndex: number;
|
||||
startTime: number | null;
|
||||
endTime: number | null;
|
||||
elapsedTime: number;
|
||||
restTimer: {
|
||||
isActive: boolean;
|
||||
duration: number;
|
||||
remaining: number;
|
||||
exerciseId?: string;
|
||||
setIndex?: number;
|
||||
};
|
||||
needsSave: boolean;
|
||||
lastSaved: number | null;
|
||||
}
|
||||
```
|
||||
|
||||
### Active Workout
|
||||
|
||||
```typescript
|
||||
interface Workout {
|
||||
id: string;
|
||||
title: string;
|
||||
type: WorkoutType;
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
isCompleted: boolean;
|
||||
templateId?: string;
|
||||
exercises: WorkoutExercise[];
|
||||
notes?: string;
|
||||
tags: string[];
|
||||
lastUpdated: number;
|
||||
}
|
||||
|
||||
interface WorkoutExercise {
|
||||
id: string;
|
||||
sourceId: string; // Reference to exercise definition
|
||||
title: string;
|
||||
sets: WorkoutSet[];
|
||||
notes?: string;
|
||||
isDirty: boolean;
|
||||
isCompleted: boolean;
|
||||
order: number;
|
||||
restTime?: number;
|
||||
}
|
||||
|
||||
interface WorkoutSet {
|
||||
id: string;
|
||||
setNumber: number;
|
||||
type: SetType;
|
||||
weight?: number;
|
||||
reps?: number;
|
||||
rpe?: number;
|
||||
isCompleted: boolean;
|
||||
isDirty: boolean;
|
||||
timestamp?: number;
|
||||
notes?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Workout Summary
|
||||
|
||||
```typescript
|
||||
interface WorkoutSummary {
|
||||
id: string;
|
||||
title: string;
|
||||
type: WorkoutType;
|
||||
duration: number; // In milliseconds
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
exerciseCount: number;
|
||||
completedExercises: number;
|
||||
totalVolume: number;
|
||||
totalReps: number;
|
||||
averageRpe?: number;
|
||||
exerciseSummaries: ExerciseSummary[];
|
||||
personalRecords: PersonalRecord[];
|
||||
}
|
||||
|
||||
interface ExerciseSummary {
|
||||
exerciseId: string;
|
||||
title: string;
|
||||
setCount: number;
|
||||
completedSets: number;
|
||||
volume: number;
|
||||
peakWeight?: number;
|
||||
totalReps: number;
|
||||
averageRpe?: number;
|
||||
}
|
||||
```
|
||||
|
||||
## SQLite Schema Integration
|
||||
|
||||
Building on the existing schema, these additional tables and relationships will be needed:
|
||||
|
||||
```sql
|
||||
-- Workout-specific schema extensions
|
||||
|
||||
-- Active workout tracking
|
||||
CREATE TABLE IF NOT EXISTS active_workouts (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
start_time INTEGER NOT NULL,
|
||||
last_updated INTEGER NOT NULL,
|
||||
template_id TEXT,
|
||||
metadata TEXT, -- JSON blob of additional data
|
||||
FOREIGN KEY(template_id) REFERENCES templates(id)
|
||||
);
|
||||
|
||||
-- Completed workouts
|
||||
CREATE TABLE IF NOT EXISTS completed_workouts (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
start_time INTEGER NOT NULL,
|
||||
end_time INTEGER NOT NULL,
|
||||
duration INTEGER NOT NULL, -- In milliseconds
|
||||
total_volume REAL,
|
||||
total_reps INTEGER,
|
||||
average_rpe REAL,
|
||||
notes TEXT,
|
||||
template_id TEXT,
|
||||
nostr_event_id TEXT,
|
||||
FOREIGN KEY(template_id) REFERENCES templates(id)
|
||||
);
|
||||
|
||||
-- Individual workout exercises
|
||||
CREATE TABLE IF NOT EXISTS workout_exercises (
|
||||
id TEXT PRIMARY KEY,
|
||||
workout_id TEXT NOT NULL,
|
||||
exercise_id TEXT NOT NULL,
|
||||
position INTEGER NOT NULL,
|
||||
is_completed BOOLEAN DEFAULT 0,
|
||||
notes TEXT,
|
||||
rest_time INTEGER, -- In seconds
|
||||
FOREIGN KEY(workout_id) REFERENCES active_workouts(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(exercise_id) REFERENCES exercises(id)
|
||||
);
|
||||
|
||||
-- Set data
|
||||
CREATE TABLE IF NOT EXISTS workout_sets (
|
||||
id TEXT PRIMARY KEY,
|
||||
workout_exercise_id TEXT NOT NULL,
|
||||
set_number INTEGER NOT NULL,
|
||||
weight REAL,
|
||||
reps INTEGER,
|
||||
rpe REAL,
|
||||
completed BOOLEAN DEFAULT 0,
|
||||
set_type TEXT NOT NULL,
|
||||
timestamp INTEGER,
|
||||
notes TEXT,
|
||||
FOREIGN KEY(workout_exercise_id) REFERENCES workout_exercises(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Personal records
|
||||
CREATE TABLE IF NOT EXISTS personal_records (
|
||||
id TEXT PRIMARY KEY,
|
||||
exercise_id TEXT NOT NULL,
|
||||
metric TEXT NOT NULL, -- 'weight', 'reps', 'volume', etc.
|
||||
value REAL NOT NULL,
|
||||
workout_id TEXT NOT NULL,
|
||||
achieved_at INTEGER NOT NULL,
|
||||
FOREIGN KEY(exercise_id) REFERENCES exercises(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(workout_id) REFERENCES completed_workouts(id)
|
||||
);
|
||||
|
||||
-- Workout tags
|
||||
CREATE TABLE IF NOT EXISTS workout_tags (
|
||||
workout_id TEXT NOT NULL,
|
||||
tag TEXT NOT NULL,
|
||||
FOREIGN KEY(workout_id) REFERENCES completed_workouts(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY(workout_id, tag)
|
||||
);
|
||||
|
||||
-- Workout statistics
|
||||
CREATE TABLE IF NOT EXISTS workout_statistics (
|
||||
workout_id TEXT PRIMARY KEY,
|
||||
stats_json TEXT NOT NULL, -- Flexible JSON storage for various metrics
|
||||
calculated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY(workout_id) REFERENCES completed_workouts(id) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
## Optimization Strategies
|
||||
|
||||
### 1. Batch Processing
|
||||
|
||||
For performance-critical operations, batch updates are used:
|
||||
|
||||
```typescript
|
||||
// Instead of individual operations
|
||||
async function saveSetsIndividually(sets: WorkoutSet[]) {
|
||||
for (const set of sets) {
|
||||
await db.runAsync(
|
||||
'UPDATE workout_sets SET weight = ?, reps = ?, completed = ? WHERE id = ?',
|
||||
[set.weight, set.reps, set.isCompleted, set.id]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Use batch operations
|
||||
async function saveSetsInBatch(sets: WorkoutSet[]) {
|
||||
if (sets.length === 0) return;
|
||||
|
||||
const placeholders = sets.map(() => '(?, ?, ?, ?, ?, ?, ?)').join(', ');
|
||||
const values = sets.flatMap(set => [
|
||||
set.id,
|
||||
set.workout_exercise_id,
|
||||
set.set_number,
|
||||
set.weight || null,
|
||||
set.reps || null,
|
||||
set.rpe || null,
|
||||
set.isCompleted ? 1 : 0
|
||||
]);
|
||||
|
||||
await db.runAsync(`
|
||||
INSERT OR REPLACE INTO workout_sets
|
||||
(id, workout_exercise_id, set_number, weight, reps, rpe, completed)
|
||||
VALUES ${placeholders}
|
||||
`, values);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Dirty Tracking
|
||||
|
||||
Optimize saves by only updating changed data:
|
||||
|
||||
```typescript
|
||||
function markDirty(entity: { isDirty?: boolean, lastUpdated?: number }) {
|
||||
entity.isDirty = true;
|
||||
entity.lastUpdated = Date.now();
|
||||
}
|
||||
|
||||
// In reducer
|
||||
case 'UPDATE_SET': {
|
||||
const { exerciseIndex, setIndex, data } = action.payload;
|
||||
const updatedExercises = [...state.activeWorkout.exercises];
|
||||
const updatedSets = [...updatedExercises[exerciseIndex].sets];
|
||||
|
||||
// Only mark as dirty if actually changed
|
||||
const currentSet = updatedSets[setIndex];
|
||||
const hasChanged = Object.entries(data).some(
|
||||
([key, value]) => currentSet[key] !== value
|
||||
);
|
||||
|
||||
if (hasChanged) {
|
||||
updatedSets[setIndex] = {
|
||||
...updatedSets[setIndex],
|
||||
...data,
|
||||
isDirty: true,
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
|
||||
updatedExercises[exerciseIndex] = {
|
||||
...updatedExercises[exerciseIndex],
|
||||
sets: updatedSets,
|
||||
isDirty: true,
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
activeWorkout: {
|
||||
...state.activeWorkout,
|
||||
exercises: updatedExercises,
|
||||
lastUpdated: hasChanged ? Date.now() : state.activeWorkout.lastUpdated,
|
||||
},
|
||||
needsSave: hasChanged,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Incremental Auto-save
|
||||
|
||||
```typescript
|
||||
function useAutoSave(
|
||||
workout: Workout | null,
|
||||
needsSave: boolean,
|
||||
saveWorkout: (workout: Workout) => Promise<void>
|
||||
) {
|
||||
const [lastSaveTime, setLastSaveTime] = useState<number | null>(null);
|
||||
const saveIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!workout) return;
|
||||
|
||||
// Set up interval for periodic saves
|
||||
saveIntervalRef.current = setInterval(() => {
|
||||
if (workout && needsSave) {
|
||||
saveWorkout(workout)
|
||||
.then(() => setLastSaveTime(Date.now()))
|
||||
.catch(err => console.error('Auto-save failed:', err));
|
||||
}
|
||||
}, 30000); // 30 seconds
|
||||
|
||||
return () => {
|
||||
if (saveIntervalRef.current) {
|
||||
clearInterval(saveIntervalRef.current);
|
||||
}
|
||||
};
|
||||
}, [workout, needsSave, saveWorkout]);
|
||||
|
||||
// Additional save on app state changes
|
||||
useAppState(
|
||||
(nextAppState) => {
|
||||
if (nextAppState === 'background' && workout && needsSave) {
|
||||
saveWorkout(workout)
|
||||
.then(() => setLastSaveTime(Date.now()))
|
||||
.catch(err => console.error('Background save failed:', err));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return lastSaveTime;
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling and Recovery
|
||||
|
||||
### 1. Save Failure Recovery
|
||||
|
||||
```typescript
|
||||
async function saveWithRetry(
|
||||
workout: Workout,
|
||||
maxRetries = 3
|
||||
): Promise<boolean> {
|
||||
let attempts = 0;
|
||||
|
||||
while (attempts < maxRetries) {
|
||||
try {
|
||||
await workoutPersistence.saveWorkout(workout);
|
||||
return true;
|
||||
} catch (error) {
|
||||
attempts++;
|
||||
console.error(`Save failed (attempt ${attempts}):`, error);
|
||||
|
||||
if (attempts >= maxRetries) {
|
||||
// Create emergency backup
|
||||
await createEmergencyBackup(workout);
|
||||
notifyUser('Workout save failed. Emergency backup created.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exponential backoff
|
||||
await new Promise(resolve =>
|
||||
setTimeout(resolve, 1000 * Math.pow(2, attempts))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function createEmergencyBackup(workout: Workout): Promise<void> {
|
||||
try {
|
||||
const backupJson = JSON.stringify(workout);
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const filename = `workout-backup-${timestamp}.json`;
|
||||
await FileSystem.writeAsStringAsync(
|
||||
`${FileSystem.documentDirectory}backups/${filename}`,
|
||||
backupJson
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Emergency backup failed:', e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Crash Recovery
|
||||
|
||||
```typescript
|
||||
async function checkForUnfinishedWorkouts(): Promise<Workout | null> {
|
||||
try {
|
||||
const activeWorkouts = await db.getAllAsync<ActiveWorkoutRow>(
|
||||
'SELECT * FROM active_workouts WHERE end_time IS NULL'
|
||||
);
|
||||
|
||||
if (activeWorkouts.length === 0) return null;
|
||||
|
||||
// Find most recent active workout
|
||||
const mostRecent = activeWorkouts.reduce((latest, current) =>
|
||||
current.last_updated > latest.last_updated ? current : latest
|
||||
);
|
||||
|
||||
// Reconstruct full workout object
|
||||
return reconstructWorkoutFromDatabase(mostRecent.id);
|
||||
} catch (error) {
|
||||
console.error('Error checking for unfinished workouts:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function useWorkoutRecovery() {
|
||||
const [recoveryWorkout, setRecoveryWorkout] = useState<Workout | null>(null);
|
||||
const [showRecoveryDialog, setShowRecoveryDialog] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkRecovery = async () => {
|
||||
const unfinishedWorkout = await checkForUnfinishedWorkouts();
|
||||
if (unfinishedWorkout) {
|
||||
setRecoveryWorkout(unfinishedWorkout);
|
||||
setShowRecoveryDialog(true);
|
||||
}
|
||||
};
|
||||
|
||||
checkRecovery();
|
||||
}, []);
|
||||
|
||||
const handleRecovery = (shouldRecover: boolean) => {
|
||||
if (shouldRecover && recoveryWorkout) {
|
||||
// Resume workout
|
||||
dispatch({
|
||||
type: 'RECOVER_WORKOUT',
|
||||
payload: recoveryWorkout
|
||||
});
|
||||
} else if (recoveryWorkout) {
|
||||
// Discard unfinished workout
|
||||
workoutPersistence.discardWorkout(recoveryWorkout.id);
|
||||
}
|
||||
|
||||
setShowRecoveryDialog(false);
|
||||
};
|
||||
|
||||
return {
|
||||
showRecoveryDialog,
|
||||
recoveryWorkout,
|
||||
handleRecovery
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Nostr Integration
|
||||
|
||||
### 1. Event Publishing
|
||||
|
||||
```typescript
|
||||
async function publishWorkoutToNostr(workout: CompletedWorkout): Promise<string> {
|
||||
try {
|
||||
// Convert to Nostr event format
|
||||
const event = createNostrWorkoutEvent(workout);
|
||||
|
||||
// Sign event
|
||||
const signedEvent = await ndk.signer.sign(event);
|
||||
|
||||
// Publish to relays
|
||||
await ndk.publish(signedEvent);
|
||||
|
||||
// Update local record with event ID
|
||||
await db.runAsync(
|
||||
'UPDATE completed_workouts SET nostr_event_id = ? WHERE id = ?',
|
||||
[signedEvent.id, workout.id]
|
||||
);
|
||||
|
||||
return signedEvent.id;
|
||||
} catch (error) {
|
||||
console.error('Failed to publish workout to Nostr:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Subscription Integration
|
||||
|
||||
```typescript
|
||||
function subscribeToWorkoutEvents() {
|
||||
// Subscribe to workout events from followed users
|
||||
const filter = {
|
||||
kinds: [33401, 33402, 33403],
|
||||
authors: followedPubkeys,
|
||||
since: lastSyncTimestamp
|
||||
};
|
||||
|
||||
const subscription = ndk.subscribe(filter);
|
||||
|
||||
subscription.on('event', (event) => {
|
||||
try {
|
||||
processIncomingNostrEvent(event);
|
||||
} catch (error) {
|
||||
console.error('Error processing incoming event:', error);
|
||||
}
|
||||
});
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
async function processIncomingNostrEvent(event: NostrEvent) {
|
||||
switch (event.kind) {
|
||||
case 33401: // Exercise definition
|
||||
await processExerciseDefinition(event);
|
||||
break;
|
||||
|
||||
case 33402: // Workout template
|
||||
await processWorkoutTemplate(event);
|
||||
break;
|
||||
|
||||
case 33403: // Workout record
|
||||
await processWorkoutRecord(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Metrics and Analytics
|
||||
|
||||
```typescript
|
||||
interface WorkoutMetrics {
|
||||
// Time metrics
|
||||
totalDuration: number;
|
||||
exerciseTime: number;
|
||||
restTime: number;
|
||||
averageSetDuration: number;
|
||||
|
||||
// Volume metrics
|
||||
totalVolume: number;
|
||||
volumeByExercise: Record<string, number>;
|
||||
volumeByMuscleGroup: Record<string, number>;
|
||||
|
||||
// Intensity metrics
|
||||
averageRpe: number;
|
||||
peakRpe: number;
|
||||
intensityDistribution: {
|
||||
low: number; // Sets with RPE 1-4
|
||||
medium: number; // Sets with RPE 5-7
|
||||
high: number; // Sets with RPE 8-10
|
||||
};
|
||||
|
||||
// Completion metrics
|
||||
exerciseCompletionRate: number;
|
||||
setCompletionRate: number;
|
||||
plannedVsActualVolume: number;
|
||||
}
|
||||
|
||||
function calculateWorkoutMetrics(workout: CompletedWorkout): WorkoutMetrics {
|
||||
// Implementation of metric calculations
|
||||
// ...
|
||||
|
||||
return metrics;
|
||||
}
|
||||
```
|
||||
|
||||
## Data Flow Timeline
|
||||
|
||||
1. **Workout Initialization** (t=0)
|
||||
- Template loaded or empty workout created
|
||||
- Initial state populated
|
||||
- Workout ID generated
|
||||
- Database record created
|
||||
|
||||
2. **Active Tracking** (t=0 → completion)
|
||||
- User inputs captured through reducers
|
||||
- State updates trigger UI refreshes
|
||||
- Dirty tracking flags changes
|
||||
- Auto-save runs periodically
|
||||
|
||||
3. **Exercise Transitions**
|
||||
- Current exercise state saved
|
||||
- Next exercise loaded
|
||||
- Progress indicators updated
|
||||
|
||||
4. **Completion Processing**
|
||||
- Final state saving
|
||||
- Statistics calculation
|
||||
- PR detection
|
||||
- Achievement unlocking
|
||||
|
||||
5. **Post-Workout**
|
||||
- History update
|
||||
- Nostr publishing (if enabled)
|
||||
- Cleanup of temporary data
|
||||
|
||||
## Integration with Existing Architecture
|
||||
|
||||
The workout data flow integrates with existing systems:
|
||||
|
||||
1. **Library System** - Templates loaded from library
|
||||
2. **User Profiles** - PRs and achievements tied to user
|
||||
3. **Social Features** - Workout sharing via Nostr
|
||||
4. **History Tab** - Completed workouts appear in history
|
||||
5. **Exercise Database** - Exercise references maintained
|
||||
|
||||
## Future Extensibility
|
||||
|
||||
This design supports future enhancements:
|
||||
|
||||
1. **AI Recommendations** - Data structured for ML analysis
|
||||
2. **External Device Integration** - Schema allows for sensor data
|
||||
3. **Advanced Periodization** - Tracking supports long-term planning
|
||||
4. **Video Analysis** - Form tracking integration points
|
||||
5. **Multi-user Workouts** - Shared workout capabilities
|
336
docs/design/WorkoutTab/WorkoutImplementationRoadmap.md
Normal file
336
docs/design/WorkoutTab/WorkoutImplementationRoadmap.md
Normal file
@ -0,0 +1,336 @@
|
||||
# POWR Workout Implementation Roadmap
|
||||
|
||||
## Overview
|
||||
|
||||
This roadmap outlines the phased implementation approach for the POWR Workout feature. The development is structured in progressive iterations, each building upon the previous while maintaining usable functionality at each stage.
|
||||
|
||||
## Phase 0: Preparation (1-2 days)
|
||||
|
||||
### Database Schema Extensions
|
||||
- Update SQLite schema with workout tables
|
||||
- Create migration scripts
|
||||
- Add test data validation
|
||||
|
||||
### TypeScript Definitions
|
||||
- Define core workout interfaces
|
||||
- Create type guards and utility functions
|
||||
- Document relationships with existing types
|
||||
|
||||
### Testing Infrastructure
|
||||
- Set up workout component testing environment
|
||||
- Create mock workout data generators
|
||||
- Establish performance benchmarks
|
||||
|
||||
## Phase 1: Core Workout Infrastructure (1 week)
|
||||
|
||||
### Workout Context & State Management
|
||||
- [ ] Create WorkoutContext and provider
|
||||
- [ ] Implement workout reducer
|
||||
- [ ] Build timer hook implementation
|
||||
- [ ] Add persistence service
|
||||
|
||||
### Basic UI Components
|
||||
- [ ] Workout setup screen
|
||||
- [ ] Basic exercise tracking interface
|
||||
- [ ] Simple completion flow
|
||||
- [ ] Minimal workout summary
|
||||
|
||||
### Database Integration
|
||||
- [ ] Basic CRUD operations for workouts
|
||||
- [ ] Template-to-workout conversion
|
||||
- [ ] Simple workout history storage
|
||||
|
||||
### Testing
|
||||
- [ ] Unit tests for core logic
|
||||
- [ ] Basic UI component tests
|
||||
- [ ] Database operation tests
|
||||
|
||||
**Milestone:** Users can start a basic workout, track sets/reps, and save results
|
||||
|
||||
## Phase 2: Enhanced Tracking Experience (1 week)
|
||||
|
||||
### Advanced UI Components
|
||||
- [ ] Rest timer implementation
|
||||
- [ ] Exercise navigation carousel
|
||||
- [ ] Set tracker with history reference
|
||||
- [ ] In-workout notes and feedback
|
||||
- [ ] Enhanced number inputs for weight/reps
|
||||
|
||||
### Workout Types Support
|
||||
- [ ] Strength workout layout
|
||||
- [ ] Circuit workout support
|
||||
- [ ] EMOM timer integration
|
||||
- [ ] AMRAP tracking
|
||||
|
||||
### Progress Tracking
|
||||
- [ ] Basic PR detection
|
||||
- [ ] Set completion visualization
|
||||
- [ ] Progress indicators
|
||||
- [ ] Exercise history comparison
|
||||
|
||||
### Data Persistence
|
||||
- [ ] Optimized auto-save
|
||||
- [ ] Crash recovery
|
||||
- [ ] Workout modification tracking
|
||||
|
||||
**Milestone:** Full-featured workout tracking with specialized workout types
|
||||
|
||||
## Phase 3: Performance & Analysis (1 week)
|
||||
|
||||
### Performance Optimization
|
||||
- [ ] Reduce render cycles
|
||||
- [ ] Optimize database operations
|
||||
- [ ] Efficient timer implementation
|
||||
- [ ] Memory usage optimization
|
||||
|
||||
### Workout Analysis
|
||||
- [ ] Comprehensive statistics calculation
|
||||
- [ ] Volume tracking by muscle group
|
||||
- [ ] Intensity analysis
|
||||
- [ ] Trend visualization
|
||||
|
||||
### Advanced Features
|
||||
- [ ] Workout templates with progression
|
||||
- [ ] RPE guidance
|
||||
- [ ] Rest period recommendations
|
||||
- [ ] Supersets and complex exercise grouping
|
||||
|
||||
### Enhanced Recovery
|
||||
- [ ] Automated backup system
|
||||
- [ ] Unfinished workout detection
|
||||
- [ ] Cross-session recovery
|
||||
|
||||
**Milestone:** Optimized workout experience with rich analytics
|
||||
|
||||
## Phase 4: Social & Nostr Integration (1 week)
|
||||
|
||||
### Nostr Event Creation
|
||||
- [ ] Exercise event formatting (kind: 33401)
|
||||
- [ ] Template event formatting (kind: 33402)
|
||||
- [ ] Workout record event formatting (kind: 33403)
|
||||
|
||||
### Publishing Flow
|
||||
- [ ] Privacy-aware publishing options
|
||||
- [ ] Selective publishing (exercises vs workouts)
|
||||
- [ ] Author attribution
|
||||
|
||||
### Subscription Integration
|
||||
- [ ] Followed user workout display
|
||||
- [ ] Workout import from Nostr
|
||||
- [ ] Social interactions (reactions, comments)
|
||||
|
||||
### Multi-device Sync
|
||||
- [ ] Workout continuation across devices
|
||||
- [ ] Template synchronization
|
||||
- [ ] History consolidation
|
||||
|
||||
**Milestone:** Social workout sharing and multi-device capabilities
|
||||
|
||||
## Phase 5: Polish & Advanced Features (1-2 weeks)
|
||||
|
||||
### UI Polish
|
||||
- [ ] Animations and transitions
|
||||
- [ ] Haptic feedback integration
|
||||
- [ ] Sound cues for timers
|
||||
- [ ] Improved visual design
|
||||
|
||||
### User Experience Improvements
|
||||
- [ ] Onboarding flow for workout tracking
|
||||
- [ ] Contextual help and tips
|
||||
- [ ] Customizable workout controls
|
||||
- [ ] Accessibility enhancements
|
||||
|
||||
### Advanced Tracking Options
|
||||
- [ ] Custom exercise metrics
|
||||
- [ ] Video form recording
|
||||
- [ ] Voice notes during workout
|
||||
- [ ] Workout photos
|
||||
|
||||
### Integration Enhancements
|
||||
- [ ] Calendar integration
|
||||
- [ ] Health kit/Google Fit synchronization
|
||||
- [ ] Export capabilities
|
||||
- [ ] Sharing to non-Nostr platforms
|
||||
|
||||
**Milestone:** Polished, feature-complete workout experience
|
||||
|
||||
## Implementation Approach
|
||||
|
||||
### Vertical Slices
|
||||
Each phase will implement features as complete vertical slices, ensuring working functionality at each stage. This approach allows for:
|
||||
|
||||
1. Earlier user testing and feedback
|
||||
2. Incremental value delivery
|
||||
3. More flexible priorities
|
||||
4. Easier integration testing
|
||||
|
||||
### Testing Strategy
|
||||
- Unit tests for all core logic components
|
||||
- Component tests for UI elements
|
||||
- Integration tests for data flow
|
||||
- E2E tests for critical user journeys
|
||||
- Performance tests for timer accuracy
|
||||
|
||||
### Quality Gates
|
||||
Each phase must meet these criteria before proceeding:
|
||||
1. All tests passing
|
||||
2. Performance benchmarks met
|
||||
3. No regressions in existing functionality
|
||||
4. Technical debt documented
|
||||
5. Documentation updated
|
||||
|
||||
## Resource Requirements
|
||||
|
||||
### Development Team
|
||||
- 1-2 React Native developers
|
||||
- 1 Designer for UI components
|
||||
- Testing support
|
||||
|
||||
### Technical Resources
|
||||
- Test devices (iOS/Android)
|
||||
- Performance monitoring setup
|
||||
- SQLite optimization tools
|
||||
- Animation prototyping tools
|
||||
|
||||
### External Dependencies
|
||||
- Nostr SDK updates if needed
|
||||
- Any third-party libraries for specific features
|
||||
- Design system extensions
|
||||
|
||||
## Risk Management
|
||||
|
||||
### Identified Risks
|
||||
1. **Timer Accuracy** - Mobile timers may drift or be affected by background state
|
||||
- *Mitigation:* Use requestAnimationFrame and background-aware implementations
|
||||
|
||||
2. **Database Performance** - Large workout histories may impact query speed
|
||||
- *Mitigation:* Implement pagination, indexing, and query optimization
|
||||
|
||||
3. **Cross-platform Consistency** - UI/interaction differences between iOS/Android/web
|
||||
- *Mitigation:* Platform-specific adaptations while maintaining consistent data model
|
||||
|
||||
4. **Offline Sync Conflicts** - Multi-device usage may cause data conflicts
|
||||
- *Mitigation:* Implement conflict resolution strategies and versioning
|
||||
|
||||
5. **Battery Usage** - Continuous timing and tracking may impact battery life
|
||||
- *Mitigation:* Optimize refresh rates, background processes, and sensor usage
|
||||
|
||||
## Success Metrics
|
||||
|
||||
### User Engagement
|
||||
- Workout completion rate
|
||||
- Average workout duration
|
||||
- Return rate to workout feature
|
||||
- Template reuse rate
|
||||
|
||||
### Performance
|
||||
- Time to interactive for workout screen
|
||||
- Save operation duration
|
||||
- Timer accuracy
|
||||
- Battery impact
|
||||
|
||||
### Quality
|
||||
- Crash-free sessions
|
||||
- Data preservation rate
|
||||
- Auto-recovery success rate
|
||||
- Cross-device consistency
|
||||
|
||||
## Future Roadmap Considerations
|
||||
|
||||
### Phase 6: Advanced Analytics
|
||||
- Machine learning for workout recommendations
|
||||
- Fatigue analysis
|
||||
- Progression optimization
|
||||
- Long-term trend visualization
|
||||
|
||||
### Phase 7: Community Features
|
||||
- Workout challenges
|
||||
- Leaderboards
|
||||
- Group workouts
|
||||
- Coach/client relationships
|
||||
|
||||
### Phase 8: Premium Features
|
||||
- Advanced workout programming
|
||||
- AI form checking
|
||||
- Premium templates
|
||||
- Personalized insights
|
||||
|
||||
## Appendix: Component Dependencies
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[WorkoutContext] --> B[WorkoutScreen]
|
||||
A --> C[TimerService]
|
||||
A --> D[WorkoutPersistence]
|
||||
|
||||
B --> E[ExerciseTracker]
|
||||
B --> F[WorkoutControls]
|
||||
B --> G[ExerciseNavigation]
|
||||
|
||||
E --> H[SetInput]
|
||||
E --> I[RestTimer]
|
||||
E --> J[RPESelector]
|
||||
|
||||
D --> K[SQLiteDatabase]
|
||||
D --> L[NostrPublisher]
|
||||
|
||||
C --> M[TimerHook]
|
||||
M --> N[BackgroundService]
|
||||
```
|
||||
|
||||
## Appendix: Data Flow Diagram
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant UI as UI Components
|
||||
participant Context as Workout Context
|
||||
participant Service as Persistence Service
|
||||
participant DB as SQLite
|
||||
participant Nostr as Nostr Protocol
|
||||
|
||||
User->>UI: Select Template
|
||||
UI->>Context: initWorkout(template)
|
||||
Context->>Service: convertTemplateToWorkout()
|
||||
Service->>DB: saveInitialWorkout()
|
||||
DB-->>Service: workoutId
|
||||
Service-->>Context: initializedWorkout
|
||||
Context-->>UI: Updated State
|
||||
|
||||
User->>UI: Record Set
|
||||
UI->>Context: updateSet(data)
|
||||
Context->>Context: Internal State Update
|
||||
Context-->>UI: Updated State
|
||||
|
||||
Note over Context: Auto-save Timer
|
||||
Context->>Service: saveIncrementalChanges()
|
||||
Service->>DB: transaction()
|
||||
|
||||
User->>UI: Complete Workout
|
||||
UI->>Context: completeWorkout()
|
||||
Context->>Service: finalizeWorkout()
|
||||
Service->>DB: saveCompletedWorkout()
|
||||
Service->>Service: calculateStatistics()
|
||||
Service->>DB: saveWorkoutStatistics()
|
||||
|
||||
opt User Enabled Sharing
|
||||
Service->>Nostr: createWorkoutEvent()
|
||||
Nostr->>Nostr: publishEvent()
|
||||
Nostr-->>Service: eventId
|
||||
Service->>DB: updateNostrEventId()
|
||||
end
|
||||
|
||||
Service-->>Context: workoutSummary
|
||||
Context-->>UI: Display Summary
|
||||
```
|
||||
|
||||
## Appendix: Test Matrix
|
||||
|
||||
| Component | Unit Tests | Integration Tests | E2E Tests | Performance Tests |
|
||||
|-----------|------------|-------------------|-----------|-------------------|
|
||||
| Workout Context | State transitions, Timer accuracy | Data flow, Component integration | - | Memory usage |
|
||||
| Exercise Tracking | Input validation, UI state | Database integration | Full workout flow | Render performance |
|
||||
| Rest Timer | Timing accuracy, State management | UI integration | - | Background behavior |
|
||||
| Database | CRUD operations | Recovery, Transaction integrity | - | Query performance |
|
||||
| Nostr Integration | Event formatting | Publishing flow | Cross-device sync | Network efficiency |
|
||||
| UI Components | Rendering, Props validation | Context integration | User interactions | Animation smoothness |
|
376
docs/design/WorkoutTab/WorkoutTabDesignDoc.md
Normal file
376
docs/design/WorkoutTab/WorkoutTabDesignDoc.md
Normal file
@ -0,0 +1,376 @@
|
||||
# POWR Workout Tab Design Document
|
||||
|
||||
## Problem Statement
|
||||
Users need a dedicated interface for tracking workout sessions in real-time, including starting new workouts from templates or creating custom workouts on the fly. The workout experience must support various workout types (strength, circuit, EMOM, AMRAP), maintain offline functionality, and prepare for future Nostr integration.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
- Start new workouts from templates or create custom workouts
|
||||
- Track sets, reps, weight, and other exercise metrics in real-time
|
||||
- Support rest timers between sets and exercises
|
||||
- Allow recording of RPE (Rate of Perceived Exertion)
|
||||
- Enable workout notes and feedback
|
||||
- Save completed workout history
|
||||
- Track PRs (Personal Records)
|
||||
- Support various workout structures (traditional, circuit, EMOM, AMRAP)
|
||||
- Provide workout summary statistics
|
||||
|
||||
### Non-Functional Requirements
|
||||
- Performant timer implementation (accurate to within 100ms)
|
||||
- Smooth UI experience during workout tracking
|
||||
- Reliable offline functionality
|
||||
- Data persistence during app crashes
|
||||
- Battery-efficient implementation
|
||||
- Support for future Nostr event publishing (kinds 33401-33403)
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### 1. Workout State Management
|
||||
**Approach:** Context-based state management with reducers
|
||||
|
||||
**Rationale:**
|
||||
- Workouts require complex state that needs to be accessed by multiple components
|
||||
- Reducer pattern provides predictable state transitions
|
||||
- Context allows state sharing without prop drilling
|
||||
- Enables isolation of workout logic from UI components
|
||||
|
||||
### 2. Timer Implementation
|
||||
**Approach:** Custom hook-based timer with requestAnimationFrame
|
||||
|
||||
**Rationale:**
|
||||
- More accurate than setInterval for visual timing
|
||||
- Better battery performance than interval-based approaches
|
||||
- Handles background/foreground transitions gracefully
|
||||
- Can be paused/resumed without drift
|
||||
|
||||
### 3. Offline Data Persistence
|
||||
**Approach:** Incremental SQLite saves with optimistic UI updates
|
||||
|
||||
**Rationale:**
|
||||
- Balances performance with data safety
|
||||
- Prevents data loss during crashes
|
||||
- Maintains responsive UI during saves
|
||||
- Supports future sync capabilities
|
||||
|
||||
### 4. Template-to-Workout Transformation
|
||||
**Approach:** Deep copy with runtime customization
|
||||
|
||||
**Rationale:**
|
||||
- Preserves template integrity
|
||||
- Allows workout-specific modifications
|
||||
- Maintains type safety
|
||||
- Supports progression tracking
|
||||
|
||||
## Technical Design
|
||||
|
||||
### Core Components
|
||||
|
||||
#### WorkoutProvider (Context)
|
||||
```typescript
|
||||
interface WorkoutContextState {
|
||||
status: 'idle' | 'active' | 'paused' | 'completed';
|
||||
activeWorkout: Workout | null;
|
||||
currentExerciseIndex: number;
|
||||
currentSetIndex: number;
|
||||
elapsedTime: number;
|
||||
restTimers: {
|
||||
isActive: boolean;
|
||||
duration: number;
|
||||
remaining: number;
|
||||
};
|
||||
}
|
||||
|
||||
type WorkoutAction =
|
||||
| { type: 'START_WORKOUT', payload: Workout }
|
||||
| { type: 'PAUSE_WORKOUT' }
|
||||
| { type: 'RESUME_WORKOUT' }
|
||||
| { type: 'COMPLETE_WORKOUT' }
|
||||
| { type: 'UPDATE_SET', payload: { exerciseIndex: number, setIndex: number, data: Partial<WorkoutSet> } }
|
||||
| { type: 'NEXT_EXERCISE' }
|
||||
| { type: 'PREVIOUS_EXERCISE' }
|
||||
| { type: 'START_REST_TIMER', payload: number }
|
||||
| { type: 'TICK_TIMER', payload: number };
|
||||
|
||||
function workoutReducer(state: WorkoutContextState, action: WorkoutAction): WorkoutContextState {
|
||||
// State transitions and logic
|
||||
}
|
||||
```
|
||||
|
||||
#### Workout Screen Structure
|
||||
```typescript
|
||||
// Main layout components
|
||||
function WorkoutScreen() {
|
||||
// Handles routing between idle/active states
|
||||
}
|
||||
|
||||
function ActiveWorkoutScreen() {
|
||||
// Active workout tracking UI
|
||||
}
|
||||
|
||||
function WorkoutSetupScreen() {
|
||||
// Template selection or custom workout creation
|
||||
}
|
||||
|
||||
function WorkoutSummaryScreen() {
|
||||
// Post-workout summary and stats
|
||||
}
|
||||
```
|
||||
|
||||
#### Timer Hook
|
||||
```typescript
|
||||
function useWorkoutTimer({
|
||||
isActive,
|
||||
onTick,
|
||||
}: {
|
||||
isActive: boolean;
|
||||
onTick: (elapsedMs: number) => void;
|
||||
}) {
|
||||
// Timer implementation using requestAnimationFrame
|
||||
// Handles background/foreground transitions
|
||||
}
|
||||
```
|
||||
|
||||
### Database Schema Extensions
|
||||
|
||||
Building on the existing schema, we'll add:
|
||||
|
||||
```sql
|
||||
-- Workout tracking
|
||||
CREATE TABLE IF NOT EXISTS workouts (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
start_time INTEGER NOT NULL,
|
||||
end_time INTEGER,
|
||||
completed BOOLEAN DEFAULT 0,
|
||||
notes TEXT,
|
||||
total_volume REAL,
|
||||
template_id TEXT,
|
||||
nostr_event_id TEXT,
|
||||
FOREIGN KEY(template_id) REFERENCES templates(id)
|
||||
);
|
||||
|
||||
-- Individual workout exercises
|
||||
CREATE TABLE IF NOT EXISTS workout_exercises (
|
||||
id TEXT PRIMARY KEY,
|
||||
workout_id TEXT NOT NULL,
|
||||
exercise_id TEXT NOT NULL,
|
||||
position INTEGER NOT NULL,
|
||||
notes TEXT,
|
||||
FOREIGN KEY(workout_id) REFERENCES workouts(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(exercise_id) REFERENCES exercises(id)
|
||||
);
|
||||
|
||||
-- Set data
|
||||
CREATE TABLE IF NOT EXISTS workout_sets (
|
||||
id TEXT PRIMARY KEY,
|
||||
workout_exercise_id TEXT NOT NULL,
|
||||
set_number INTEGER NOT NULL,
|
||||
weight REAL,
|
||||
reps INTEGER,
|
||||
rpe REAL,
|
||||
completed BOOLEAN DEFAULT 0,
|
||||
set_type TEXT NOT NULL,
|
||||
timestamp INTEGER,
|
||||
FOREIGN KEY(workout_exercise_id) REFERENCES workout_exercises(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Personal records
|
||||
CREATE TABLE IF NOT EXISTS personal_records (
|
||||
id TEXT PRIMARY KEY,
|
||||
exercise_id TEXT NOT NULL,
|
||||
metric TEXT NOT NULL,
|
||||
value REAL NOT NULL,
|
||||
workout_id TEXT NOT NULL,
|
||||
achieved_at INTEGER NOT NULL,
|
||||
FOREIGN KEY(exercise_id) REFERENCES exercises(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(workout_id) REFERENCES workouts(id)
|
||||
);
|
||||
```
|
||||
|
||||
### TypeScript Definitions
|
||||
|
||||
```typescript
|
||||
// Workout Types
|
||||
export interface Workout {
|
||||
id: string;
|
||||
title: string;
|
||||
type: WorkoutType;
|
||||
exercises: WorkoutExercise[];
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
isCompleted: boolean;
|
||||
notes?: string;
|
||||
templateId?: string;
|
||||
totalVolume?: number;
|
||||
}
|
||||
|
||||
export type WorkoutType = 'strength' | 'circuit' | 'emom' | 'amrap';
|
||||
|
||||
// PR Tracking
|
||||
export interface PersonalRecord {
|
||||
id: string;
|
||||
exerciseId: string;
|
||||
metric: 'weight' | 'reps' | 'volume';
|
||||
value: number;
|
||||
workoutId: string;
|
||||
achievedAt: number;
|
||||
}
|
||||
```
|
||||
|
||||
## User Interface Design
|
||||
|
||||
### Workout Flow
|
||||
|
||||
#### 1. Idle State / Setup
|
||||
- Option to start from template
|
||||
- Option to create custom workout
|
||||
- Quick-start recent workouts
|
||||
- Template browsing/filtering
|
||||
|
||||
#### 2. Active Workout Screens
|
||||
- **Header:** Workout title, timer, complete button
|
||||
- **Exercise Navigation:** Current exercise, navigation controls
|
||||
- **Set Tracking:** Weight/reps input, rest timer, RPE selection
|
||||
- **Notes:** Exercise-specific notes field
|
||||
- **Progress:** Visual indicators of completion status
|
||||
|
||||
#### 3. Summary Screen
|
||||
- Workout duration
|
||||
- Total volume
|
||||
- PR achievements
|
||||
- Exercise completion rates
|
||||
- Option to add notes
|
||||
- Share capabilities (future Nostr publishing)
|
||||
|
||||
### UI Components
|
||||
|
||||
#### WorkoutHeader
|
||||
Displays current status, elapsed time, and workout controls
|
||||
|
||||
#### ExerciseTracker
|
||||
Primary interface for tracking sets of current exercise
|
||||
|
||||
#### RestTimer
|
||||
Visual countdown with sound/vibration alerts
|
||||
|
||||
#### WorkoutSummary
|
||||
Post-workout statistics and achievements
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Core Workout Flow (2 weeks)
|
||||
1. Implement WorkoutContext and reducer
|
||||
2. Build workout setup screen
|
||||
3. Create basic exercise tracking UI
|
||||
4. Implement timer functionality
|
||||
5. Add SQLite persistence for workouts
|
||||
|
||||
### Phase 2: Advanced Features (2 weeks)
|
||||
1. Implement rest timers with alerts
|
||||
2. Add PR tracking and detection
|
||||
3. Create workout summary screen
|
||||
4. Support for different workout types
|
||||
5. Add notes and feedback options
|
||||
|
||||
### Phase 3: Polish & Integration (1 week)
|
||||
1. UI refinements and animations
|
||||
2. Performance optimization
|
||||
3. Integration with Nostr publishing
|
||||
4. Add sharing capabilities
|
||||
5. Final testing and bug fixes
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- Timer accuracy and pause/resume functionality
|
||||
- State transitions in workout reducer
|
||||
- Template transformation logic
|
||||
- PR detection algorithms
|
||||
- Exercise progression calculations
|
||||
|
||||
### Integration Tests
|
||||
- Workout flow from setup to completion
|
||||
- Data persistence during app lifecycle events
|
||||
- Template-to-workout conversion
|
||||
- History recording accuracy
|
||||
|
||||
### Performance Tests
|
||||
- Timer precision under load
|
||||
- UI responsiveness during data saving
|
||||
- Battery usage monitoring
|
||||
- Memory profiling during long workouts
|
||||
|
||||
## Observability
|
||||
|
||||
### Logging
|
||||
- Workout state transitions
|
||||
- Timer accuracy metrics
|
||||
- Data save operations
|
||||
- Error conditions
|
||||
|
||||
### Analytics (Future)
|
||||
- Workout completion rates
|
||||
- Average workout duration
|
||||
- Most used templates
|
||||
- Feature usage statistics
|
||||
|
||||
## Future Considerations
|
||||
|
||||
### Potential Enhancements
|
||||
- Voice feedback during workouts
|
||||
- Video form checking integration
|
||||
- Social sharing via Nostr
|
||||
- Workout streaks and achievements
|
||||
- AI-powered workout recommendations
|
||||
- Heart rate monitor integration
|
||||
- Barcode scanner for gym equipment
|
||||
|
||||
### Known Limitations
|
||||
- Timer may drift slightly in background
|
||||
- Workout types limited to predefined structures
|
||||
- No direct hardware integrations in MVP
|
||||
- Offline-only in initial implementation
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Runtime Dependencies
|
||||
- SQLite for data persistence
|
||||
- Timer implementation libraries
|
||||
- Chart visualization for summary
|
||||
- Haptic feedback for timers
|
||||
|
||||
### Development Dependencies
|
||||
- Testing framework for timer accuracy
|
||||
- Mock data generators
|
||||
- Performance monitoring tools
|
||||
|
||||
## Security Considerations
|
||||
- Personal fitness data privacy
|
||||
- Optional anonymization for shared workouts
|
||||
- Secure storage of personal records
|
||||
- Permission handling for notifications
|
||||
|
||||
## Rollout Strategy
|
||||
|
||||
### Development Phase
|
||||
1. Implement core workout tracking
|
||||
2. Add template integration
|
||||
3. Build timer functionality
|
||||
4. Create persistence layer
|
||||
5. Add summary statistics
|
||||
|
||||
### Production Deployment
|
||||
1. Internal testing with sample workouts
|
||||
2. Beta testing with power users
|
||||
3. Phased rollout to all users
|
||||
4. Monitoring for performance issues
|
||||
5. Iterative improvements based on feedback
|
||||
|
||||
## References
|
||||
- Nostr NIP-33401: Exercise Templates
|
||||
- Nostr NIP-33402: Workout Templates
|
||||
- Nostr NIP-33403: Workout Records
|
||||
- React Native Animation Performance Guide
|
||||
- SQLite Transaction Best Practices
|
348
docs/design/WorkoutTab/WorkoutUIComponentSpec.md
Normal file
348
docs/design/WorkoutTab/WorkoutUIComponentSpec.md
Normal file
@ -0,0 +1,348 @@
|
||||
# Workout UI Component Specification
|
||||
|
||||
## Overview
|
||||
This document outlines the key UI components needed for the POWR workout tracking experience. The interface prioritizes readability during exercise, quick data entry, and clear visualization of progress.
|
||||
|
||||
## Core UI Components
|
||||
|
||||
### 1. Workout Header
|
||||

|
||||
|
||||
```tsx
|
||||
interface WorkoutHeaderProps {
|
||||
title: string;
|
||||
type: WorkoutType;
|
||||
elapsedTime: number;
|
||||
isActive: boolean;
|
||||
onPause: () => void;
|
||||
onResume: () => void;
|
||||
onComplete: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
#### Features
|
||||
- Sticky header with minimized height
|
||||
- Elapsed time with large, readable timer
|
||||
- Workout title and type indicator
|
||||
- Status indicators (active/paused)
|
||||
- Action buttons (pause/resume/complete)
|
||||
- Optional: calorie/heart rate display
|
||||
|
||||
#### Behavior
|
||||
- Time updates every second
|
||||
- Color changes based on active/paused state
|
||||
- Confirm dialog appears before completing workout
|
||||
|
||||
---
|
||||
|
||||
### 2. Exercise Navigation
|
||||

|
||||
|
||||
```tsx
|
||||
interface ExerciseNavigationProps {
|
||||
exercises: Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
isCompleted: boolean;
|
||||
}>;
|
||||
currentIndex: number;
|
||||
onSelectExercise: (index: number) => void;
|
||||
onNext: () => void;
|
||||
onPrevious: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
#### Features
|
||||
- Horizontal scroll for exercise list
|
||||
- Current exercise highlighted
|
||||
- Progress indicators showing completion status
|
||||
- Previous/next navigation controls
|
||||
- Jump-to capability for non-linear workouts
|
||||
|
||||
#### Behavior
|
||||
- Swipe gestures to change exercises
|
||||
- Auto-scrolls to keep current exercise visible
|
||||
- Vibration feedback on exercise change
|
||||
- Optional confirmation when leaving incomplete exercise
|
||||
|
||||
---
|
||||
|
||||
### 3. Set Tracker
|
||||

|
||||
|
||||
```tsx
|
||||
interface SetTrackerProps {
|
||||
sets: WorkoutSet[];
|
||||
exercise: WorkoutExercise;
|
||||
onUpdateSet: (setIndex: number, data: Partial<WorkoutSet>) => void;
|
||||
onAddSet: () => void;
|
||||
onRemoveSet: (setIndex: number) => void;
|
||||
showRestTimer: boolean;
|
||||
onStartRest: (duration: number) => void;
|
||||
}
|
||||
```
|
||||
|
||||
#### Features
|
||||
- Individual set cards with weight/reps/RPE inputs
|
||||
- Completion toggle for each set
|
||||
- Previous set data for reference
|
||||
- Support for different input types based on exercise format
|
||||
- "Add Set" button for additional sets
|
||||
- Rest timer trigger
|
||||
|
||||
#### Behavior
|
||||
- Auto-focuses appropriate input field
|
||||
- Supports quick incrementing/decrementing of values
|
||||
- Auto-suggests rest time based on set intensity
|
||||
- Remembers input patterns within workout
|
||||
- Validates inputs against exercise constraints
|
||||
|
||||
---
|
||||
|
||||
### 4. Rest Timer
|
||||

|
||||
|
||||
```tsx
|
||||
interface RestTimerProps {
|
||||
duration: number;
|
||||
remaining: number;
|
||||
isActive: boolean;
|
||||
onPause: () => void;
|
||||
onResume: () => void;
|
||||
onSkip: () => void;
|
||||
onExtend: (seconds: number) => void;
|
||||
}
|
||||
```
|
||||
|
||||
#### Features
|
||||
- Large circular countdown display
|
||||
- Visual progress indicator
|
||||
- Time remaining in large font
|
||||
- Control buttons (pause/resume/skip)
|
||||
- Quick-extend buttons (+30s, +1m)
|
||||
- Next exercise preview
|
||||
|
||||
#### Behavior
|
||||
- Full-screen takeover when active
|
||||
- Haptic feedback at 50% and 10% remaining
|
||||
- Sound alert at completion (if enabled)
|
||||
- Auto-dismisses after completion
|
||||
- Background timer continues running
|
||||
- Screen prevents sleep during active timer
|
||||
|
||||
---
|
||||
|
||||
### 5. Exercise Details Panel
|
||||

|
||||
|
||||
```tsx
|
||||
interface ExerciseDetailsPanelProps {
|
||||
exercise: WorkoutExercise;
|
||||
previousPerformance?: {
|
||||
date: number;
|
||||
sets: WorkoutSet[];
|
||||
personalBests: Record<string, number>;
|
||||
};
|
||||
onAddNote: (note: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
#### Features
|
||||
- Collapsible panel for exercise details
|
||||
- Form instructions and tips
|
||||
- Previous performance metrics
|
||||
- Personal best indicators
|
||||
- Notes field for technique reminders
|
||||
- Optional media previews (images/video)
|
||||
|
||||
#### Behavior
|
||||
- Expandable/collapsible with smooth animation
|
||||
- Auto-collapses during timer to maximize screen space
|
||||
- Persists notes between workout sessions
|
||||
- Highlights personal records
|
||||
|
||||
---
|
||||
|
||||
### 6. Workout Controls
|
||||

|
||||
|
||||
```tsx
|
||||
interface WorkoutControlsProps {
|
||||
canComplete: boolean;
|
||||
onPause: () => void;
|
||||
onResume: () => void;
|
||||
onCancel: () => void;
|
||||
onComplete: () => void;
|
||||
isActive: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
#### Features
|
||||
- Fixed position at screen bottom
|
||||
- Primary action button (Complete Workout)
|
||||
- Secondary actions (pause/resume)
|
||||
- Cancel workout option
|
||||
- Status indicators
|
||||
|
||||
#### Behavior
|
||||
- Complete button enables when minimum criteria met
|
||||
- Confirmation dialog for cancel action
|
||||
- Smooth transition animations between states
|
||||
- Haptic feedback on major actions
|
||||
|
||||
---
|
||||
|
||||
### 7. Workout Summary
|
||||

|
||||
|
||||
```tsx
|
||||
interface WorkoutSummaryProps {
|
||||
workout: CompletedWorkout;
|
||||
achievements: {
|
||||
personalRecords: PersonalRecord[];
|
||||
streaks: Streak[];
|
||||
milestones: Milestone[];
|
||||
};
|
||||
onSave: (notes: string) => void;
|
||||
onShare: () => void;
|
||||
onDiscard: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
#### Features
|
||||
- Comprehensive workout statistics
|
||||
- Total volume, duration, and intensity metrics
|
||||
- Exercise completion breakdown
|
||||
- Personal records achieved
|
||||
- Notes field for workout reflection
|
||||
- Visual charts of performance
|
||||
- Share and save controls
|
||||
|
||||
#### Behavior
|
||||
- Scrollable container for all summary data
|
||||
- Expandable sections for detailed stats
|
||||
- Animated entry of achievement cards
|
||||
- Pre-populates notes from during-workout entries
|
||||
- Save confirmation with preview
|
||||
|
||||
## Layout Variations
|
||||
|
||||
### 1. Strength Workout Layout
|
||||
Optimized for tracking weight, reps and rest periods.
|
||||
|
||||
- Prominent weight/rep inputs
|
||||
- Set-rest-set pattern flow
|
||||
- Previous lift stats readily visible
|
||||
- PR tracking indicators
|
||||
- Weight plate calculator
|
||||
|
||||
### 2. Circuit Workout Layout
|
||||
Designed for quick transitions between exercises.
|
||||
|
||||
- Minimized input fields
|
||||
- Prominent exercise timer
|
||||
- Next exercise preview
|
||||
- Round counter
|
||||
- Overall circuit progress
|
||||
|
||||
### 3. EMOM/AMRAP Layout
|
||||
Focused on timed intervals and rep counting.
|
||||
|
||||
- Large interval timer
|
||||
- Quick rep counter
|
||||
- Round progression
|
||||
- Work/rest indicators
|
||||
- Audio cues for intervals
|
||||
|
||||
## Interaction Patterns
|
||||
|
||||
### 1. Data Entry
|
||||
- Single-tap to select input field
|
||||
- Long-press for quick increment/decrement
|
||||
- Swipe to mark set complete
|
||||
- Shake to undo last action
|
||||
- Double-tap to copy previous set values
|
||||
|
||||
### 2. Navigation
|
||||
- Swipe between exercises
|
||||
- Pull down to reveal workout overview
|
||||
- Pull up for exercise details
|
||||
- Pinch to zoom workout timeline
|
||||
- Double-tap header to toggle timer visibility
|
||||
|
||||
### 3. Timers
|
||||
- Tap timer to pause/resume
|
||||
- Swipe up on timer for fullscreen mode
|
||||
- Rotate device for alternative timer view
|
||||
- Shake to skip timer
|
||||
- Volume buttons as quick controls
|
||||
|
||||
## Accessibility Considerations
|
||||
|
||||
### 1. Visual
|
||||
- High contrast mode for gym environments
|
||||
- Large text option for all metrics
|
||||
- Color-blind friendly progress indicators
|
||||
- Screen rotation lock option
|
||||
- Auto-brightness adjustment
|
||||
|
||||
### 2. Motor
|
||||
- Large touch targets for sweaty hands
|
||||
- Voice control for hands-free operation
|
||||
- Simplified layout option with fewer controls
|
||||
- Adjustable button sensitivity
|
||||
- Support for external Bluetooth controls
|
||||
|
||||
### 3. Auditory
|
||||
- Vibration patterns as alternative to sound
|
||||
- Visual countdown alternatives
|
||||
- Adjustable volume levels
|
||||
- Custom sound selection
|
||||
- Background noise filtering for voice features
|
||||
|
||||
## State Transitions
|
||||
|
||||
### 1. Idle → Active
|
||||
- Template selection or quick start
|
||||
- Exercise preview animation
|
||||
- Timer initialization
|
||||
- Welcome guidance (configurable)
|
||||
|
||||
### 2. Active → Paused
|
||||
- Dim UI elements
|
||||
- Prominent resume button
|
||||
- Elapsed time continues but visually distinguished
|
||||
- Quick access to notes and adjustments
|
||||
|
||||
### 3. Active → Complete
|
||||
- Celebration animation
|
||||
- Stats calculation overlay
|
||||
- Achievement unlocks
|
||||
- Social share prompts (optional)
|
||||
- Return to home or next workout suggestion
|
||||
|
||||
## Theme Integration
|
||||
|
||||
All components should support both light and dark themes with special considerations:
|
||||
|
||||
1. **Dark gym mode** - Ultra-dark background with high contrast elements for poorly lit environments
|
||||
2. **Outdoor mode** - High contrast, glare-resistant design for outdoor workouts
|
||||
3. **Night mode** - Red-shifted colors to minimize blue light during evening workouts
|
||||
4. **Energy saver** - Minimalist UI with reduced animations to maximize battery life
|
||||
|
||||
## Component Integration
|
||||
|
||||
These components will integrate with the existing POWR architecture:
|
||||
|
||||
1. **Component Library** - Extends existing UI components with workout-specific variants
|
||||
2. **Theme System** - Utilizes current theme tokens with workout-specific additions
|
||||
3. **Navigation** - Embeds within the tab navigation as a modal flow when active
|
||||
4. **Context** - Consumes the WorkoutContext for state management
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Create low-fidelity wireframes for each component
|
||||
2. Develop component prototypes using existing design system
|
||||
3. Test input patterns with sample workout data
|
||||
4. Validate timer accuracy across devices
|
||||
5. Create component documentation in Storybook
|
Loading…
x
Reference in New Issue
Block a user