diff --git a/CHANGELOG.md b/CHANGELOG.md index a3976fc..84c1dbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ All notable changes to the POWR project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# Changelog - March 26, 2025 + +## Fixed +- Authentication state management issues + - Fixed runtime error when signing out from social screens + - Enhanced useSocialFeed hook with better subscription management + - Improved NDK logout process with proper subscription cleanup + - Added deep comparison for subscription parameters to prevent unnecessary resubscriptions + - Implemented exponential backoff for subscription attempts + - Enhanced error handling in subscription lifecycle + - Fixed React hooks order issues in social components + - Added proper cleanup of subscriptions during authentication state changes + - Increased logout delay to ensure proper cleanup of resources + - Added type-safe access to NDK internal properties + - Fixed "Rendered fewer hooks than expected" error during logout + - Ensured consistent hook call order in social feed components + - Improved subscription cleanup timing in NDK store + - Enhanced state management during authentication transitions + - Added better subscription tracking and cleanup in logout process + # Changelog - March 25, 2025 ## Added diff --git a/docs/architecture/authentication.md b/docs/architecture/authentication.md new file mode 100644 index 0000000..87a850d --- /dev/null +++ b/docs/architecture/authentication.md @@ -0,0 +1,212 @@ +# Authentication Architecture + +**Last Updated:** 2025-03-25 +**Status:** Active +**Related To:** Nostr Integration, State Management + +## Purpose + +This document describes the authentication architecture of the POWR app, focusing on Nostr-based authentication, key management, and the state machine implementation needed for the MVP. + +## Overview + +Authentication in POWR is built on the Nostr protocol, which uses public key cryptography. The system: + +1. Manages user keypairs for Nostr authentication +2. Maintains clear login/logout state +3. Securely stores private keys on device +4. Implements a proper state machine for auth transitions +5. Supports offline capabilities with cached authentication + +## Component Architecture + +### High-Level Components + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ UI Layer │ │ Service Layer │ │ Storage Layer │ +│ │ │ │ │ │ +│ Login Prompt │ │ Auth Manager │ │ Secure Storage │ +│ Auth State UI │◄───►│ State Machine │◄───►│ NDK Signer │ +│ Profile Display │ │ Relay Manager │ │ Session Storage │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +### Auth State Machine + +The core of the authentication architecture is a state machine with these states: + +1. **Unauthenticated**: No valid authentication exists +2. **Authenticating**: Authentication process in progress +3. **Authenticated**: User is fully authenticated +4. **Deauthenticating**: Logout process in progress + +This state machine handles all transitions between these states, ensuring consistent authentication behavior. + +### Implementation + +The authentication system uses Zustand for state management: + +```typescript +// Auth store implementation example +interface AuthState { + // State + state: 'unauthenticated' | 'authenticating' | 'authenticated' | 'deauthenticating'; + user: { pubkey: string; metadata?: any } | null; + error: Error | null; + + // Actions + login: (privateKeyOrNpub: string) => Promise; + createAccount: () => Promise; + logout: () => Promise; +} + +const useAuthStore = create((set, get) => ({ + state: 'unauthenticated', + user: null, + error: null, + + login: async (privateKeyOrNpub) => { + try { + set({ state: 'authenticating', error: null }); + + // Implementation details for key import + // NDK setup with signer + // Profile fetching + + set({ state: 'authenticated', user: { pubkey: '...' } }); + } catch (error) { + set({ state: 'unauthenticated', error }); + } + }, + + createAccount: async () => { + try { + set({ state: 'authenticating', error: null }); + + // Generate new keypair + // Save to secure storage + // NDK setup with signer + + set({ state: 'authenticated', user: { pubkey: '...' } }); + } catch (error) { + set({ state: 'unauthenticated', error }); + } + }, + + logout: async () => { + try { + set({ state: 'deauthenticating' }); + + // Clean up connections + // Clear sensitive data + + set({ state: 'unauthenticated', user: null }); + } catch (error) { + // Even on error, force logout + set({ state: 'unauthenticated', user: null, error }); + } + } +})); +``` + +### Key Storage and Management + +Private keys are stored using: + +- **iOS**: Keychain Services +- **Android**: Android Keystore System +- **Both**: Expo SecureStore wrapper + +The keys are never exposed directly to application code; instead, a signer interface is used that can perform cryptographic operations without exposing the private key. + +### MVP Implementation Focus + +For the MVP release, the authentication system focuses on: + +1. **Robust State Management** + - Clear state transitions + - Error handling for each state + - Proper event tracking + +2. **Basic Auth Flows** + - Login with npub or nsec (with security warnings) + - Account creation + - Reliable logout + +3. **Key Security** + - Secure storage of private keys + - Zero exposure of private keys in app memory + - Proper cleanup on logout + +### Integration Points + +The authentication system integrates with: + +1. **NDK Instance** + - Provides signer for NDK + - Manages relay connections + - Triggers cleanup on logout + +2. **Profile Management** + - Fetches user profile on login + - Updates profile metadata + - Handles associated data loading + +3. **UI Components** + - Login/create account prompt + - Authentication state indicators + - Profile display components + +## Known Issues and Solutions + +### Current Issues + +1. **Inconsistent Auth State** + - Problem: Multiple components updating auth state cause race conditions + - Solution: Centralized state machine with explicit transitions + +2. **Incomplete Logout** + - Problem: Resources not properly cleaned up on logout + - Solution: Comprehensive cleanup in deauthenticating state + +3. **Subscription Cleanup** + - Problem: Subscriptions not tied to auth lifecycle + - Solution: Link subscription management to auth state changes + +### State Transitions + +Handling auth state transitions properly: + +``` +┌───────────────┐ ┌───────────────┐ +│ │ │ │ +│Unauthenticated│ │ Authenticated │ +│ │ │ │ +└───────┬───────┘ └───────┬───────┘ + │ │ + │ login/ │ logout + │ createAccount │ + ▼ ▼ +┌───────────────┐ ┌───────────────┐ +│ │ success │ │ +│Authenticating │─────────────────────────────►│Deauthenticating│ +│ │ │ │ +└───────────────┘ └───────────────┘ + │ │ + │ error │ always + │ │ + ▼ ▼ +┌───────────────┐ ┌───────────────┐ +│ │ │ │ +│Unauthenticated│◄─────────────────────────────┤Unauthenticated│ +│ (with error)│ │ │ +└───────────────┘ └───────────────┘ +``` + +## Related Documentation + +- [NDK Comprehensive Guide](../technical/ndk/comprehensive_guide.md) - NDK implementation +- [Subscription Analysis](../technical/ndk/subscription_analysis.md) - Subscription management +- [Profile Features](../features/profile/index.md) - Profile integration +- [MVP and Targeted Rebuild](../project/mvp_and_rebuild.md) - Overall MVP strategy diff --git a/docs/architecture/index.md b/docs/architecture/index.md new file mode 100644 index 0000000..b6202a7 --- /dev/null +++ b/docs/architecture/index.md @@ -0,0 +1,211 @@ +# POWR App Architecture + +**Last Updated:** 2025-03-25 +**Status:** Active + +## Purpose + +This document provides an overview of the POWR app's architecture, including key design decisions, component organization, and technical patterns used throughout the application. + +## Architecture Overview + +POWR is built as a React Native application using Expo, with a local-first architecture that prioritizes offline functionality while supporting Nostr protocol integration for social features. + +### Key Architectural Principles + +1. **Local-First Design**: Primary data stored locally with cloud synchronization +2. **Component-Based UI**: Modular React components with clear responsibilities +3. **State Management Separation**: Business logic separated from UI components +4. **Clean Service Layer**: Service abstractions for data access and operations +5. **Protocol Integration**: Nostr protocol integration for social features +6. **Mobile-Optimized Performance**: Performance considerations for mobile constraints + +## High-Level Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ UI Layer │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Workout │ │ Library │ │ Social │ ... │ +│ │ Components │ │ Components │ │ Components │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└────────────────────────────┬────────────────────────────────┘ + │ +┌────────────────────────────┴────────────────────────────────┐ +│ State Management │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Workout │ │ Library │ │ Nostr │ ... │ +│ │ Store │ │ Store │ │ Store │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└────────────────────────────┬────────────────────────────────┘ + │ +┌────────────────────────────┴────────────────────────────────┐ +│ Service Layer │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Workout │ │ Template │ │ Social │ ... │ +│ │ Services │ │ Services │ │ Services │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└────────────────────────────┬────────────────────────────────┘ + │ +┌────────────────────────────┴────────────────────────────────┐ +│ Data Layer │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ SQLite │ │ Nostr NDK │ │ Cache │ ... │ +│ │ Database │ │ Interface │ │ System │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Key Architectural Components + +### UI Layer + +The UI layer consists of React components organized by feature domains and following a component composition pattern: + +- **Feature Screens**: Top-level screens for each major feature area +- **Composite Components**: Reusable components combining multiple base components +- **Base Components**: Simple, reusable UI elements with minimal logic +- **UI Primitives**: Foundational styled components following design system + +### State Management + +State management uses a combination of approaches: + +- **Zustand Stores**: For global application state (auth, workouts, etc.) +- **React Context**: For feature-specific shared state +- **Local Component State**: For UI-specific ephemeral state +- **Custom Hooks**: For encapsulating state logic and side effects + +### Service Layer + +Services provide an abstraction over data operations: + +- **Data Services**: Handle CRUD operations for specific entity types +- **Integration Services**: Manage external system integration (e.g., Nostr) +- **Utility Services**: Provide cross-cutting functionality (logging, analytics) +- **Process Services**: Orchestrate complex operations across multiple services + +### Data Layer + +The data layer handles persistent storage and external data access: + +- **SQLite Database**: Primary storage for local data +- **NDK Interface**: Nostr protocol integration +- **Caching System**: Performance optimization for frequently used data +- **Offline Queue**: Management of operations during offline periods + +## Key Design Patterns + +### Repository Pattern + +Data access is abstracted through repositories that provide domain-specific interfaces to the underlying storage: + +```typescript +// Example repository +class WorkoutRepository { + // Get all workouts + async getAll(): Promise {...} + + // Get workout by ID + async getById(id: string): Promise {...} + + // Save a workout + async save(workout: Workout): Promise {...} + + // Delete a workout + async delete(id: string): Promise {...} +} +``` + +### Service Pattern + +Business logic is encapsulated in services that operate on the domain model: + +```typescript +// Example service +class WorkoutService { + constructor( + private workoutRepo: WorkoutRepository, + private exerciseRepo: ExerciseRepository + ) {} + + // Complete a workout + async completeWorkout(workout: Workout): Promise { + // Business logic here + workout.completedAt = new Date(); + await this.workoutRepo.save(workout); + // Additional operations + } +} +``` + +### State Machine Pattern + +Complex state transitions use explicit state machines to manage allowed transitions and side effects: + +```typescript +// Example state machine for auth +const authStateMachine = { + unauthenticated: { + login: 'authenticating', + createAccount: 'authenticating' + }, + authenticating: { + success: 'authenticated', + error: 'unauthenticated' + }, + authenticated: { + logout: 'deauthenticating' + }, + deauthenticating: { + always: 'unauthenticated' + } +}; +``` + +### Adapter Pattern + +External systems are integrated through adapters that normalize the interface: + +```typescript +// Example adapter for Nostr +class NostrAdapter implements SocialPlatformAdapter { + // Post a message + async postMessage(content: string): Promise { + // Nostr-specific implementation + } + + // Get messages from following + async getFollowingFeed(): Promise { + // Nostr-specific implementation + } +} +``` + +## Folder Structure + +The application code is organized by feature and technical concern: + +``` +/app - App routes and pages + /(tabs) - Main tab screens + /(workout) - Workout flow screens + /(social) - Social flow screens +/components - React components + /ui - Base UI components + /workout - Workout-specific components + /social - Social-specific components +/lib - Core application code + /db - Database services + /hooks - Custom React hooks + /stores - State management +/types - TypeScript type definitions +/utils - Utility functions +``` + +## Related Documentation + +- [Authentication](./authentication.md) - Authentication architecture details +- [State Management](./state_management.md) - State management approach +- [NDK Integration](../technical/ndk/comprehensive_guide.md) - NDK implementation details +- [MVP and Targeted Rebuild](../project/mvp_and_rebuild.md) - Implementation strategy diff --git a/docs/archive/DocumentationImplementationScript.md b/docs/archive/DocumentationImplementationScript.md new file mode 100644 index 0000000..c2c91d4 --- /dev/null +++ b/docs/archive/DocumentationImplementationScript.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/project/documentation/implementation_script.md](../project/documentation/implementation_script.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/DocumentationOrganizationPlan.md b/docs/archive/DocumentationOrganizationPlan.md new file mode 100644 index 0000000..15c4a78 --- /dev/null +++ b/docs/archive/DocumentationOrganizationPlan.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/project/documentation/organization_plan.md](../project/documentation/organization_plan.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/DocumentationReviewPrompt.md b/docs/archive/DocumentationReviewPrompt.md new file mode 100644 index 0000000..bdb899c --- /dev/null +++ b/docs/archive/DocumentationReviewPrompt.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/project/documentation/review_process.md](../project/documentation/review_process.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/NDKSubscriptionAnalysis.md b/docs/archive/NDKSubscriptionAnalysis.md new file mode 100644 index 0000000..2b16c4b --- /dev/null +++ b/docs/archive/NDKSubscriptionAnalysis.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/technical/ndk/subscription_analysis.md](../technical/ndk/subscription_analysis.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/NDK_Comprehensive_Guide.md b/docs/archive/NDK_Comprehensive_Guide.md new file mode 100644 index 0000000..c0813fc --- /dev/null +++ b/docs/archive/NDK_Comprehensive_Guide.md @@ -0,0 +1,543 @@ +# ARCHIVED: NDK Comprehensive Guide for POWR App + +**ARCHIVED:** This document has been superseded by [docs/technical/ndk/comprehensive_guide.md](../technical/ndk/comprehensive_guide.md) as part of the documentation reorganization. + +**UPDATED FOR MVP: This guide has been consolidated and aligned with our simplified social approach for the MVP release.** + +This guide combines key information from our various NDK analysis documents and the ndk-mobile package to provide a comprehensive reference for implementing Nostr features in POWR app. It's organized to support our MVP strategy with a focus on the core NDK capabilities we need. + +## Table of Contents + +1. [NDK Core Concepts](#ndk-core-concepts) +2. [Proper State Management](#proper-state-management) +3. [Subscription Lifecycle](#subscription-lifecycle) +4. [NIP-19 and Encoding/Decoding](#nip-19-and-encodingdecoding) +5. [Context Provider Pattern](#context-provider-pattern) +6. [Mobile-Specific Considerations](#mobile-specific-considerations) +7. [Best Practices for POWR MVP](#best-practices-for-powr-mvp) + +## NDK Core Concepts + +NDK (Nostr Development Kit) provides a set of tools for working with the Nostr protocol. Key components include: + +- **NDK Instance**: Central object for interacting with the Nostr network +- **NDKEvent**: Represents a Nostr event +- **NDKUser**: Represents a user (pubkey) +- **NDKFilter**: Defines criteria for querying events +- **NDKSubscription**: Manages subscriptions to event streams +- **NDKRelay**: Represents a connection to a relay + +### Basic Setup + +```typescript +import NDK from '@nostr-dev-kit/ndk'; + +// Initialize NDK with relays +const ndk = new NDK({ + explicitRelayUrls: [ + 'wss://relay.damus.io', + 'wss://relay.nostr.band' + ] +}); + +// Connect to relays +await ndk.connect(); +``` + +## Proper State Management + +The ndk-mobile library emphasizes proper state management using Zustand. This approach is critical for avoiding the issues we've been seeing in our application. + +### Zustand Store Pattern + +```typescript +// Create a singleton store using Zustand +import { create } from 'zustand'; + +// Private store instance - not exported +let store: ReturnType | undefined; + +// Store creator function +const createNDKStore = () => create((set) => ({ + ndk: undefined, + initialized: false, + connecting: false, + connectionError: null, + + initialize: (config) => set((state) => { + if (state.initialized) return state; + + const ndk = new NDK(config); + return { ndk, initialized: true }; + }), + + connect: async () => { + set({ connecting: true, connectionError: null }); + try { + const ndk = store?.getState().ndk; + if (!ndk) throw new Error("NDK not initialized"); + + await ndk.connect(); + set({ connecting: false }); + } catch (error) { + set({ connecting: false, connectionError: error }); + } + } +})); + +// Getter for the singleton store +export const getNDKStore = () => { + if (!store) { + store = createNDKStore(); + } + return store; +}; + +// Hook for components to use +export const useNDKStore = (selector: (state: NDKState) => T) => { + // Ensure the store exists + getNDKStore(); + // Return the selected state + return useStore(store)(selector); +}; +``` + +### Critical State Management Points + +1. **Single Store Instance**: Always use a singleton pattern for NDK stores +2. **Lazy Initialization**: Only create the store when first accessed +3. **Proper Selectors**: Select only what you need from the store +4. **Clear State Transitions**: Define explicit state transitions (connecting, connected, error) + +## Subscription Lifecycle + +Proper subscription management is crucial for app stability and performance. The subscribe.ts file in ndk-mobile provides advanced subscription handling. + +### Basic Subscription Pattern + +```typescript +import { useEffect } from 'react'; +import { useNDK } from './hooks/useNDK'; + +function EventComponent({ filter }) { + const { ndk } = useNDK(); + const [events, setEvents] = useState([]); + + useEffect(() => { + if (!ndk) return; + + // Create subscription + const sub = ndk.subscribe(filter, { + closeOnEose: false, // Keep connection open + }); + + // Handle incoming events + sub.on('event', (event) => { + setEvents(prev => [...prev, event]); + }); + + // Start subscription + sub.start(); + + // Critical: Clean up subscription when component unmounts + return () => { + sub.stop(); + }; + }, [ndk, JSON.stringify(filter)]); + + return (/* render events */); +} +``` + +### Enhanced Subscription Hook + +The ndk-mobile package includes an enhanced useSubscribe hook with additional features: + +```typescript +// Example based on ndk-mobile implementation +function useEnhancedSubscribe(filter, options = {}) { + const { ndk } = useNDK(); + const [events, setEvents] = useState([]); + const [eose, setEose] = useState(false); + const subRef = useRef(null); + + useEffect(() => { + if (!ndk) return; + + // Create subscription + const sub = ndk.subscribe(filter, { + closeOnEose: options.closeOnEose || false, + wrap: options.wrap || false + }); + + subRef.current = sub; + + // Handle incoming events + sub.on('event', (event) => { + // Process events (filtering, wrapping, etc.) + + // Add to state + setEvents(prev => { + // Check for duplicates using event.id + if (prev.some(e => e.id === event.id)) return prev; + return [...prev, event]; + }); + }); + + // Handle end of stored events + sub.on('eose', () => { + setEose(true); + }); + + // Start subscription + sub.start(); + + // Clean up + return () => { + if (subRef.current) { + subRef.current.stop(); + } + }; + }, [ndk, JSON.stringify(filter), options]); + + return { events, eose }; +} +``` + +## NIP-19 and Encoding/Decoding + +NIP-19 functions are essential for handling Nostr identifiers like npub, note, and naddr. + +### Decoding NIP-19 Entities + +```typescript +import { nip19 } from '@nostr-dev-kit/ndk'; + +// Decode any NIP-19 entity (naddr, npub, nsec, note, etc.) +function decodeNIP19(encoded: string) { + try { + const decoded = nip19.decode(encoded); + // decoded.type will be 'npub', 'note', 'naddr', etc. + // decoded.data will contain the data specific to that type + return decoded; + } catch (error) { + console.error('Invalid NIP-19 format:', error); + return null; + } +} + +// Convert npub to hex pubkey +function npubToHex(npub: string) { + try { + const decoded = nip19.decode(npub); + if (decoded.type === 'npub') { + return decoded.data as string; // This is the hex pubkey + } + return null; + } catch (error) { + console.error('Invalid npub format:', error); + return null; + } +} +``` + +### Encoding to NIP-19 Formats + +```typescript +// Create an npub from a hex public key +function hexToNpub(hexPubkey: string) { + return nip19.npubEncode(hexPubkey); +} + +// Create a note (event reference) from event ID +function eventIdToNote(eventId: string) { + return nip19.noteEncode(eventId); +} + +// Create an naddr for addressable events +function createNaddr(pubkey: string, kind: number, identifier: string) { + return nip19.naddrEncode({ + pubkey, // Hex pubkey + kind, // Event kind (number) + identifier // The 'd' tag value + }); +} +``` + +## Context Provider Pattern + +The ndk-mobile package emphasizes the Context Provider pattern for proper NDK integration: + +```typescript +import { NDKProvider } from '@nostr-dev-kit/ndk-mobile'; +import App from './App'; + +// Root component +export default function Root() { + return ( + + + + ); +} +``` + +This pattern ensures: + +1. **Single NDK Instance**: The entire app shares one NDK instance +2. **Consistent State**: Auth state and relay connections are managed in one place +3. **Hooks Availability**: All NDK hooks (useNDK, useSubscribe, etc.) work correctly +4. **Proper Cleanup**: Connections and subscriptions are managed appropriately + +## Mobile-Specific Considerations + +The ndk-mobile package includes several mobile-specific optimizations: + +### Caching Strategy + +Mobile devices need efficient caching to reduce network usage and improve performance: + +```typescript +import { SQLiteAdapter } from '@nostr-dev-kit/ndk-mobile/cache-adapter/sqlite'; + +// Initialize NDK with SQLite caching +const ndk = new NDK({ + explicitRelayUrls: [...], + cacheAdapter: new SQLiteAdapter({ + dbName: 'nostr-cache.db' + }) +}); +``` + +### Mobile Signers + +For secure key management on mobile devices: + +```typescript +import { SecureStorageSigner } from '@nostr-dev-kit/ndk-mobile/signers/securestorage'; + +// Use secure storage for private keys +const signer = new SecureStorageSigner({ + storageKey: 'nostr-private-key' +}); + +// Add to NDK +ndk.signer = signer; +``` + +## Best Practices for POWR MVP + +Based on our analysis and the ndk-mobile implementation, here are key best practices for our MVP: + +### 1. State Management + +- **Use singleton stores** for NDK and session state +- **Implement proper state machine** for auth transitions with Zustand +- **Add event listeners/callbacks** for components to respond to auth changes + +```typescript +// Authentication state using Zustand (aligned with ndk-mobile patterns) +export const useAuthStore = create((set) => ({ + state: 'unauthenticated', // 'unauthenticated', 'authenticating', 'authenticated', 'deauthenticating' + user: null, + error: null, + + startAuthentication: async (npub) => { + set({ state: 'authenticating', error: null }); + try { + // Authentication logic here + const user = await authenticateUser(npub); + set({ state: 'authenticated', user }); + } catch (error) { + set({ state: 'unauthenticated', error }); + } + }, + + logout: async () => { + set({ state: 'deauthenticating' }); + // Cleanup logic + set({ state: 'unauthenticated', user: null }); + }, +})); +``` + +### 2. Subscription Management + +- **Always clean up subscriptions** when components unmount +- **Keep subscription references** in useRef to ensure proper cleanup +- **Use appropriate cache strategies** to reduce relay load +- **Implement proper error handling** for subscription failures + +```typescript +// Example component with proper subscription management +function WorkoutShareComponent({ workoutId }) { + const { ndk } = useNDK(); + const [relatedPosts, setRelatedPosts] = useState([]); + const subRef = useRef(null); + + useEffect(() => { + if (!ndk) return; + + // Create subscription for posts mentioning this workout + const sub = ndk.subscribe({ + kinds: [1], + '#e': [workoutId] + }, { + closeOnEose: true, + cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST + }); + + subRef.current = sub; + + // Handle events + sub.on('event', (event) => { + setRelatedPosts(prev => [...prev, event]); + }); + + // Start subscription + sub.start(); + + // Clean up on unmount + return () => { + if (subRef.current) { + subRef.current.stop(); + } + }; + }, [ndk, workoutId]); + + // Component JSX +} +``` + +### 3. Simplified Social Features + +For the MVP, we're focusing on: + +- **Workout sharing**: Publishing kind 1301 workout records and kind 1 notes quoting them +- **Official feed**: Display only POWR official account posts in the social tab +- **Static content**: Prefer static content over complex real-time feeds + +```typescript +// Example function to share a workout +async function shareWorkout(ndk, workout, caption) { + // First, publish the workout record (kind 1301) + const workoutEvent = new NDKEvent(ndk); + workoutEvent.kind = 1301; + workoutEvent.content = JSON.stringify(workout); + // Add appropriate tags + workoutEvent.tags = [ + ['d', workout.id], + ['title', workout.title], + ['duration', workout.duration.toString()] + ]; + + // Publish workout record + const workoutPub = await workoutEvent.publish(); + + // Then create a kind 1 note quoting the workout + const noteEvent = new NDKEvent(ndk); + noteEvent.kind = 1; + noteEvent.content = caption || `Just completed ${workout.title}!`; + // Add e tag to reference the workout event + noteEvent.tags = [ + ['e', workoutEvent.id, '', 'quote'] + ]; + + // Publish social note + await noteEvent.publish(); + + return { workoutEvent, noteEvent }; +} +``` + +### 4. Error Handling & Offline Support + +- **Implement graceful fallbacks** for network errors +- **Store pending publish operations** for later retry +- **Use SQLite caching** for offline access to previously loaded data + +```typescript +// Publication queue service example (aligned with mobile patterns) +class PublicationQueue { + async queueForPublication(event) { + try { + // Try immediate publication + await event.publish(); + return true; + } catch (error) { + // Store for later retry + await this.storeEventForLater(event); + return false; + } + } + + async publishPendingEvents() { + const pendingEvents = await this.getPendingEvents(); + for (const event of pendingEvents) { + try { + await event.publish(); + await this.markAsPublished(event); + } catch (error) { + // Keep in queue for next retry + console.error("Failed to publish:", error); + } + } + } +} +``` + +### 5. Provider Pattern + +- **Use NDKProvider** to wrap the application +- **Configure NDK once** at the app root level +- **Access NDK via hooks** rather than creating instances + +```typescript +// App.tsx +export default function App() { + return ( + + + + ); +} + +// Using NDK in components +function SomeComponent() { + const { ndk } = useNDK(); + + // Use ndk here + + return (/* component JSX */); +} +``` + +### 6. MVP Implementation Focus + +For the MVP, focus on implementing: + +1. **Core Authentication**: Proper login/logout with state management +2. **Workout Sharing**: Publication of workouts to Nostr +3. **Limited Social Features**: Static feed of official POWR account +4. **User Profile**: Basic user information display + +Defer these for post-MVP: +1. Full social feed implementation +2. Real-time following/interaction +3. Complex subscription patterns + +By following these best practices, we can create a stable foundation for the POWR app's MVP release that addresses the current architectural issues while providing a simplified but functional social experience. diff --git a/docs/archive/NDKandNip19.md b/docs/archive/NDKandNip19.md new file mode 100644 index 0000000..2534356 --- /dev/null +++ b/docs/archive/NDKandNip19.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/technical/nostr/encoding_decoding.md](../technical/nostr/encoding_decoding.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/POWRSocialArchitecture.md b/docs/archive/POWRSocialArchitecture.md new file mode 100644 index 0000000..445a7ef --- /dev/null +++ b/docs/archive/POWRSocialArchitecture.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/features/social/architecture.md](../features/social/architecture.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/POWRSocialFeedImplementationPlan.md b/docs/archive/POWRSocialFeedImplementationPlan.md new file mode 100644 index 0000000..89a7a96 --- /dev/null +++ b/docs/archive/POWRSocialFeedImplementationPlan.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/features/social/feed_implementation_details.md](../features/social/feed_implementation_details.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/POWR_Pack.md b/docs/archive/POWR_Pack.md new file mode 100644 index 0000000..19380b0 --- /dev/null +++ b/docs/archive/POWR_Pack.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/features/powr_packs/overview.md](../features/powr_packs/overview.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/POWR_Pack_Implementation_Plan.md b/docs/archive/POWR_Pack_Implementation_Plan.md new file mode 100644 index 0000000..9407f34 --- /dev/null +++ b/docs/archive/POWR_Pack_Implementation_Plan.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/features/powr_packs/implementation_plan.md](../features/powr_packs/implementation_plan.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/SocialFeedCacheImplementation.md b/docs/archive/SocialFeedCacheImplementation.md new file mode 100644 index 0000000..bc822c5 --- /dev/null +++ b/docs/archive/SocialFeedCacheImplementation.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/features/social/cache_implementation.md](../features/social/cache_implementation.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/SocialFeedFilteringRules.md b/docs/archive/SocialFeedFilteringRules.md new file mode 100644 index 0000000..579df69 --- /dev/null +++ b/docs/archive/SocialFeedFilteringRules.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/features/social/feed_filtering.md](../features/social/feed_filtering.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/UpdatedPlan.md b/docs/archive/UpdatedPlan.md new file mode 100644 index 0000000..292d658 --- /dev/null +++ b/docs/archive/UpdatedPlan.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/features/social/implementation_plan.md](../features/social/implementation_plan.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/WorkoutDataFlowSpec.md b/docs/archive/WorkoutDataFlowSpec.md new file mode 100644 index 0000000..bc51360 --- /dev/null +++ b/docs/archive/WorkoutDataFlowSpec.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/features/workout/data_models.md](../features/workout/data_models.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/WorkoutTabDesignDoc.md b/docs/archive/WorkoutTabDesignDoc.md new file mode 100644 index 0000000..a9e9042 --- /dev/null +++ b/docs/archive/WorkoutTabDesignDoc.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/features/workout/workout_overview.md](../features/workout/workout_overview.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/WorkoutUIComponentSpec.md b/docs/archive/WorkoutUIComponentSpec.md new file mode 100644 index 0000000..39bde92 --- /dev/null +++ b/docs/archive/WorkoutUIComponentSpec.md @@ -0,0 +1,5 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/features/workout/ui_components.md](../features/workout/ui_components.md) as part of the documentation reorganization. + +The original content has been enhanced and structured according to our new documentation standards. Please refer to the new location for the most up-to-date information. diff --git a/docs/archive/nostr-exercise-nip.md b/docs/archive/nostr-exercise-nip.md new file mode 100644 index 0000000..d862c73 --- /dev/null +++ b/docs/archive/nostr-exercise-nip.md @@ -0,0 +1,307 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/technical/nostr/exercise_nip.md](../technical/nostr/exercise_nip.md) as part of the documentation reorganization. + +# NIP-4e: Workout Events + +`draft` `optional` + +This specification defines workout events for fitness tracking. These workout events support both planning (templates) and recording (completed activities). + +## Event Kinds + +### Event Kind Selection Rationale + +The event kinds in this NIP follow Nostr protocol conventions: + +- **Exercise and Workout Templates** (33401, 33402) use parameterized replaceable event kinds (30000+) because: + - They represent content that may be updated or improved over time + - The author may want to replace previous versions with improved ones + - They need the `d` parameter to distinguish between different templates by the same author + - Multiple versions shouldn't accumulate in clients' storage + +- **Workout Records** (1301) use a standard event kind (0-9999) because: + - They represent a chronological feed of activity that shouldn't replace previous records + - Each workout is a unique occurrence that adds to a user's history + - Users publish multiple records over time, creating a timeline + - They're conceptually similar to notes (kind 1) but with structured fitness data + +### Exercise Template (kind: 33401) +Defines reusable exercise definitions. These should remain public to enable discovery and sharing. The `content` field contains detailed form instructions and notes. + +#### Format + +The format uses an _addressable event_ of `kind:33401`. + +The `.content` of these events SHOULD be detailed instructions for proper exercise form. It is required but can be an empty string. + +The list of tags are as follows: + +* `d` (required) - universally unique identifier (UUID). Generated by the client creating the exercise template. +* `title` (required) - Exercise name +* `format` (required) - Defines data structure for exercise tracking (possible parameters: `weight`, `reps`, `rpe`, `set_type`) +* `format_units` (required) - Defines units for each parameter (possible formats: "kg", "count", "0-10", "warmup|normal|drop|failure") +* `equipment` (required) - Equipment type (possible values: `barbell`, `dumbbell`, `bodyweight`, `machine`, `cardio`) +* `difficulty` (optional) - Skill level (possible values: `beginner`, `intermediate`, `advanced`) +* `imeta` (optional) - Media metadata for form demonstrations following NIP-92 format +* `t` (optional, repeated) - Hashtags for categorization such as muscle group or body movement (possible values: `chest`, `legs`, `push`, `pull`) + +``` +{ + "id": <32-bytes lowercase hex-encoded SHA-256 of the serialized event data>, + "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, + "created_at": , + "kind": 33401, + "content": "", + "tags": [ + ["d", ""], + ["title", ""], + ["format", "", "", "", ""], + ["format_units", "", "", "", ""], + ["equipment", ""], + ["difficulty", ""], + ["imeta", + "url ", + "m ", + "dim ", + "alt " + ], + ["t", ""], + ["t", ""], + ["t", ""] + ] +} +``` + +### Workout Template (kind: 33402) +Defines a complete workout plan. The `content` field contains workout notes and instructions. Workout templates can prescribe specific parameters while leaving others configurable by the user performing the workout. + +#### Format + +The format uses an _addressable event_ of `kind:33402`. + +The `.content` of these events SHOULD contain workout notes and instructions. It is required but can be an empty string. + +The list of tags are as follows: + +* `d` (required) - universally unique identifier (UUID). Generated by the client creating the workout template. +* `title` (required) - Workout name +* `type` (required) - Type of workout (possible values: `strength`, `circuit`, `emom`, `amrap`) +* `exercise` (required, repeated) - Exercise reference and prescription. Format: ["exercise", "::", "", ...parameters matching exercise template format] +* `rounds` (optional) - Number of rounds for repeating formats +* `duration` (optional) - Total workout duration in seconds +* `interval` (optional) - Duration of each exercise portion in seconds (for timed workouts) +* `rest_between_rounds` (optional) - Rest time between rounds in seconds +* `t` (optional, repeated) - Hashtags for categorization + +``` +{ + "id": <32-bytes lowercase hex-encoded SHA-256 of the serialized event data>, + "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, + "created_at": , + "kind": 33402, + "content": "", + "tags": [ + ["d", ""], + ["title", ""], + ["type", ""], + ["rounds", ""], + ["duration", ""], + ["interval", ""], + ["rest_between_rounds", ""], + ["exercise", "::", "", "", "", "", ""], + ["exercise", "::", "", "", "", "", ""], + ["t", ""], + ["t", ""] + ] +} +``` + +### Workout Record (kind: 1301) +Records a completed workout session. The `content` field contains notes about the workout. + +#### Format + +The format uses a standard event of `kind:1301`. + +The `.content` of these events SHOULD contain notes about the workout experience. It is required but can be an empty string. + +The list of tags are as follows: + +* `d` (required) - universally unique identifier (UUID). Generated by the client creating the workout record. +* `title` (required) - Workout name +* `type` (required) - Type of workout (possible values: `strength`, `circuit`, `emom`, `amrap`) +* `exercise` (required, repeated) - Exercise reference and completion data. Format: ["exercise", "::", "", ...parameters matching exercise template format] +* `start` (required) - Unix timestamp in seconds for workout start +* `end` (required) - Unix timestamp in seconds for workout end +* `completed` (required) - Boolean indicating if workout was completed as planned +* `rounds_completed` (optional) - Number of rounds completed +* `interval` (optional) - Duration of each exercise portion in seconds (for timed workouts) +* `template` (optional) - Reference to the workout template used, if any. Format: ["template", "::", ""] +* `pr` (optional, repeated) - Personal Record achieved during workout. Format: "::,," +* `t` (optional, repeated) - Hashtags for categorization + +``` +{ + "id": <32-bytes lowercase hex-encoded SHA-256 of the serialized event data>, + "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, + "created_at": , + "kind": 1301, + "content": "", + "tags": [ + ["d", ""], + ["title", ""], + ["type", ""], + ["rounds_completed", ""], + ["start", ""], + ["end", ""], + + ["exercise", "::", "", "", "", "", ""], + ["exercise", "::", "", "", "", "", ""], + + ["template", "::", ""], + ["pr", "::,,"], + ["completed", ""], + ["t", ""], + ["t", ""] + ] +} +``` + +## Exercise Parameters + +### Standard Parameters and Units +* `weight` - Load in kilograms (kg). Empty string for bodyweight exercises, negative values for assisted exercises +* `reps` - Number of repetitions (count) +* `rpe` - Rate of Perceived Exertion (0-10): + - RPE 10: Could not do any more reps, technical failure + - RPE 9: Could maybe do 1 more rep + - RPE 8: Could definitely do 1 more rep, maybe 2 + - RPE 7: Could do 2-3 more reps +* `duration` - Time in seconds +* `set_type` - Set classification (possible values: `warmup`, `normal`, `drop`, `failure`) + +Additional parameters can be defined in exercise templates in the `format_units` tag as needed for specific activities (e.g., distance, heartrate, intensity). + +## Workout Types and Terminology + +This specification provides examples of common workout structures but is not limited to these types. The format is extensible to support various training methodologies while maintaining consistent data structure. + +### Common Workout Types + +#### Strength +Traditional strength training focusing on sets and reps with defined weights. Typically includes warm-up sets, working sets, and may include techniques like drop sets or failure sets. + +#### Circuit +Multiple exercises performed in sequence with minimal rest between exercises and defined rest periods between rounds. Focuses on maintaining work rate through prescribed exercises. + +#### EMOM (Every Minute On the Minute) +Time-based workout where specific exercises are performed at the start of each minute. Rest time is whatever remains in the minute after completing prescribed work. + +#### AMRAP (As Many Rounds/Reps As Possible) +Time-capped workout where the goal is to complete as many rounds or repetitions as possible of prescribed exercises while maintaining proper form. + +## Set Types + +### Normal Sets +Standard working sets that count toward volume and progress tracking. + +### Warm-up Sets +Preparatory sets using submaximal weights. These sets are not counted in metrics or progress tracking. + +### Drop Sets +Sets performed immediately after a working set with reduced weight. These are counted in volume calculations but tracked separately for progress analysis. + +### Failure Sets +Sets where technical failure was reached before completing prescribed reps. These sets are counted in metrics but marked to indicate intensity/failure was reached. + +## Examples + +### Exercise Template +``` +{ + "kind": 33401, + "content": "Stand with feet hip-width apart, barbell over midfoot. Hinge at hips, grip bar outside knees. Flatten back, brace core. Drive through floor, keeping bar close to legs.\n\nForm demonstration: https://powr.me/exercises/deadlift-demo.mp4", + "tags": [ + ["d", ""], + ["title", "Barbell Deadlift"], + ["format", "weight", "reps", "rpe", "set_type"], + ["format_units", "kg", "count", "0-10", "warmup|normal|drop|failure"], + ["equipment", "barbell"], + ["difficulty", "intermediate"], + ["imeta", + "url https://powr.me/exercises/deadlift-demo.mp4", + "m video/mp4", + "dim 1920x1080", + "alt Demonstration of proper barbell deadlift form" + ], + ["t", "compound"], + ["t", "legs"], + ["t", "posterior"] + ] +} +``` + +### EMOM Workout Template +``` +{ + "kind": 33402, + "content": "20 minute EMOM alternating between squats and deadlifts every 30 seconds. Scale weight as needed to complete all reps within each interval.", + "tags": [ + ["d", ""], + ["title", "20min Squat/Deadlift EMOM"], + ["type", "emom"], + ["duration", "1200"], + ["rounds", "20"], + ["interval", "30"], + + ["exercise", "33401::", "", "", "5", "7", "normal"], + ["exercise", "33401::", "", "", "4", "7", "normal"], + + ["t", "conditioning"], + ["t", "legs"] + ] +} +``` + +### Circuit Workout Record +``` +{ + "kind": 1301, + "content": "Completed first round as prescribed. Second round showed form deterioration on deadlifts.", + "tags": [ + ["d", ""], + ["title", "Leg Circuit"], + ["type", "circuit"], + ["rounds_completed", "1.5"], + ["start", "1706454000"], + ["end", "1706455800"], + + ["exercise", "33401::", "", "80", "12", "7", "normal"], + ["exercise", "33401::", "", "100", "10", "7", "normal"], + + ["exercise", "33401::", "", "80", "12", "8", "normal"], + ["exercise", "33401::", "", "100", "4", "10", "failure"], + + ["completed", "false"], + ["t", "legs"] + ] +} +``` + +## Implementation Guidelines + +1. All workout records MUST include accurate start and end times +2. Templates MAY prescribe specific parameters while leaving others as empty strings for user input +3. Records MUST include actual values for all parameters defined in exercise format +4. Failed sets SHOULD be marked with `failure` set_type +5. Records SHOULD be marked as `false` for completed if prescribed work wasn't completed +6. PRs SHOULD only be tracked in workout records, not templates +7. Exercise references MUST use the format "kind:pubkey:d-tag" to ensure proper attribution and versioning + +## References + +This NIP draws inspiration from: +- [NIP-01: Basic Protocol Flow Description](https://github.com/nostr-protocol/nips/blob/master/01.md) +- [NIP-52: Calendar Events](https://github.com/nostr-protocol/nips/blob/master/52.md) +- [NIP-92: Media Attachments](https://github.com/nostr-protocol/nips/blob/master/92.md#nip-92) diff --git a/docs/check-links.js b/docs/check-links.js new file mode 100644 index 0000000..77c01b1 --- /dev/null +++ b/docs/check-links.js @@ -0,0 +1,48 @@ +#!/usr/bin/env node +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const docsRoot = path.resolve(__dirname); + +// Get all markdown files +const allMdFiles = execSync(`find ${docsRoot} -name "*.md"`).toString().split('\n').filter(Boolean); + +// Track all files and links +const allFiles = new Set(allMdFiles.map(f => path.relative(docsRoot, f))); +const brokenLinks = []; + +allMdFiles.forEach(file => { + const content = fs.readFileSync(file, 'utf8'); + const relativeFile = path.relative(docsRoot, file); + + // Find markdown links + const linkRegex = /\[.*?\]\((.*?)\)/g; + let match; + + while ((match = linkRegex.exec(content)) !== null) { + const link = match[1]; + + // Skip external links and anchors + if (link.startsWith('http') || link.startsWith('#')) continue; + + // Resolve relative to the file + const fileDir = path.dirname(relativeFile); + const resolvedLink = path.normalize(path.join(fileDir, link)); + + // Check if file exists + if (!allFiles.has(resolvedLink)) { + brokenLinks.push({ file: relativeFile, link, resolvedLink }); + } + } +}); + +if (brokenLinks.length > 0) { + console.log('Found broken links:'); + brokenLinks.forEach(({ file, link, resolvedLink }) => { + console.log(`In ${file}: Broken link ${link} (resolves to ${resolvedLink})`); + }); + process.exit(1); +} else { + console.log('All links are valid!'); +} \ No newline at end of file diff --git a/docs/design/MVPandTargetedRebuild.md b/docs/design/MVPandTargetedRebuild.md new file mode 100644 index 0000000..9f1422e --- /dev/null +++ b/docs/design/MVPandTargetedRebuild.md @@ -0,0 +1,204 @@ +# ARCHIVED: This document has moved + +**NOTE:** This document has been migrated to [docs/project/mvp_and_rebuild.md](../project/mvp_and_rebuild.md) as part of the documentation reorganization. + +# POWR App Roadmap - MVP and Social Rebuild + +## MVP Definition + +The Minimum Viable Product (MVP) will focus on core functionality while simplifying social features: + +### Core Features (MVP Priority) +- Complete workout tracking and history +- Exercise library and template management +- POWR Pack support +- Basic Nostr integration: + - Ability to publish kind 1301 workout records + - Ability to share workouts with kind 1 notes (quoting 1301 records) + - NIP-89 compliance for app identification + +### Simplified Social Implementation +- Social tab with "Coming Soon" placeholder or minimal POWR official feed +- Profile tab with limited social activity display +- Workout sharing from completion flow (with simplified UI) +- Add workout sharing from history tab + +## Current Technical Challenges + +### Authentication Issues +- Inconsistent auth state management causing cascading problems +- Logout process triggering uncoordinated state changes + +### Subscription Management Problems +- Subscription lifecycle not properly managed +- Resources not being cleaned up consistently + +### React Hook Implementation +- "Rendered fewer hooks than expected" errors +- Component lifecycle hook management issues + +### Component Coupling +- Tight interdependencies between components +- Difficulty isolating fixes for individual components + +## Implementation Phases + +### Phase 1: MVP Stabilization (Current Focus) +- Implement fundamental architecture improvements: + - Authentication state management with clear lifecycle hooks + - Basic subscription management improvements +- Simplify or disable problematic social features +- Add workout sharing from history tab +- Ensure stable workout tracking, history, and template management +- Fix critical bugs in core functionality + +### Phase 2: Social Foundation Rebuild (Post-MVP) +- Complete targeted rebuild of authentication and subscription management +- Implement proper data layer with caching +- Create clear separation between data and UI layers +- Develop and test in parallel with MVP branch + +### Phase 3: Social Feature Re-implementation +- Gradually re-enable social features using new architecture +- Start with simplest screens (e.g., official POWR feed) +- Progress to more complex screens (Following, Global) +- Implement enhanced profile activity view + +### Phase 4: Extended Features +- Amber integration for Android users +- Enhanced social features beyond original implementation +- Additional Nostr integrations and social capabilities + +## Architecture Design + +### 1. Authentication State Management +- Implementation of a proper state machine pattern +- Clear transitions: unauthenticated → authenticating → authenticated → deauthenticating +- Use of Zustand store (aligned with current workoutStore approach) +- Event listeners/callbacks for components to respond to auth changes + +### 2. Subscription Management +- Centralized service for managing subscriptions +- Automatic tracking and cleanup of subscriptions +- Integration with component lifecycle +- Rate limiting and cooldown mechanisms + +### 3. Data Layer Design +- Clean separation between data fetching and UI components +- Proper caching with expiration policies +- Offline support strategy +- Clear interfaces for data services + +### 4. UI Component Structure +- Consistent component patterns across social features +- Proper error boundaries and loading states +- Better separation of concerns between components +- Rebuilt social feed components with cleaner architecture + +## Git and Release Strategy + +### Branch Strategy +- Create `mvp` branch from current state +- Implement MVP simplifications and critical fixes in this branch +- In parallel, start architecture rebuild in `social-rebuild` branch +- Once MVP is released, gradually merge rebuilt components from `social-rebuild` to `main` + +### Feature Flag Implementation +- Add configuration system for feature toggling +- Create conditional rendering for social features +- Define clear interfaces between components to allow swapping implementations +- Store feature flag state in persistent storage for consistency across app launches + +### Release Plan +1. iOS TestFlight (MVP) +2. Implement Amber integration and final Android preparations +3. Android Google Play / APK release +4. Gradual social feature re-enablement through app updates + +## Key Files to Modify + +### MVP Initial Changes +- `app/(tabs)/social/_layout.tsx` - Add "Coming Soon" placeholder or simplified view +- `components/workout/WorkoutCompletionFlow.tsx` - Ensure sharing functionality is stable +- `lib/db/services/NostrWorkoutService.ts` - Review for stability and proper NIP-89 implementation +- `app/(tabs)/history/workoutHistory.tsx` - Add sharing capability + +### Core Architecture Improvements +- `lib/stores/ndk.ts` - Enhance with better auth management +- `lib/hooks/useNDK.ts` - Refactor for more predictable state management +- `components/RelayInitializer.tsx` - Review for subscription management issues +- `lib/hooks/useSubscribe.ts` - Improve subscription lifecycle management + +### Future Rebuild Targets (Post-MVP) +- `lib/hooks/useSocialFeed.ts` - Replace with new service +- `lib/social/socialFeedService.ts` - Refactor with cleaner architecture +- `app/(tabs)/social/*` - Rebuild social feed screens with new architecture +- `components/social/*` - Rebuild social components with consistent patterns + +## Development Timeline + +### 1. Architecture Design: 2-3 days +- Create detailed service interfaces +- Design state management approach +- Document component lifecycle integration + +### 2. Core Service Implementation: 3-5 days +- Build authentication manager +- Implement subscription manager +- Create data fetching services + +### 3. UI Component Rebuild: 5-7 days +- Rebuild one screen at a time +- Implement with new architectural patterns +- Add comprehensive error handling + +### 4. Testing and Integration: 2-3 days +- Test with various network conditions +- Verify authentication edge cases +- Confirm subscription cleanup + +### 5. Cleanup and Documentation: 1-2 days +- Remove deprecated code +- Document new architecture +- Create developer onboarding guide + +## Risk Mitigation +- Implement feature flags to toggle between old and new implementations +- Add enhanced logging during transition +- Create robust error boundaries to prevent cascade failures +- Maintain backward compatibility for core APIs during migration + +## Original Requirements and Questions + +### Simplified MVP Social Experience +- Minimal or no social feed +- Replace social tab with "Coming Soon" placeholder +- Focus on core functionality: + - Allow users to post kind 1 notes quoting 1301 workout records + - Publishing workflow: + 1. User performs workout + 2. User completes workout and opts to share publicly + 3. User edits pre-populated kind 1 note and submits + 4. App publishes kind 1301 workout record, then publishes kind 1 note quoting the record + 5. Result: kind 1 note published to socials, kind 1301 record visible in workout history + - Implement NIP-89 for app identification in published records + +### Key Questions Addressed + +#### Impact on Workout History Functionality +The targeted rebuild approach preserves workout history functionality by focusing primarily on problematic social components. Core authentication and subscription management improvements will benefit the entire app without disrupting workflow. + +#### MVP Architecture Requirements +For a stable MVP with limited social features, we recommend implementing the fundamental Authentication state management and Subscription Management components. These are foundational and will improve stability across all features that use Nostr integration. + +#### Caching Services +Existing caching for user metadata can likely be preserved with clearer interfaces. For the MVP, we can simplify how these caches are used rather than fully rebuilding them. + +#### Workout History Sharing +Adding the ability to share workouts from the history tab would be valuable and consistent with the completion flow sharing functionality. This will require a review of local vs. Nostr event strategies. + +#### Amber Integration +Amber integration should be prioritized after the initial iOS TestFlight release but before wider Android distribution. + +#### Git Strategy +Creating an `mvp` branch from the current state makes sense for the MVP implementation. The feature flag approach will allow gradual introduction of rebuilt components without disrupting the user experience. diff --git a/docs/design/ProfileTab/ProfileTabEnhancementDesignDoc.md b/docs/design/ProfileTab/ProfileTabEnhancementDesignDoc.md index 83f734f..2685625 100644 --- a/docs/design/ProfileTab/ProfileTabEnhancementDesignDoc.md +++ b/docs/design/ProfileTab/ProfileTabEnhancementDesignDoc.md @@ -1,3 +1,5 @@ +**This document needs to be updated as we switched the order of the personal social feed (first) and the overview. We should also update this with the progress we made and some of the setbacks such as the react hooks consistency issues, user authentication state managment subscription management issues and component interdependencies** + # Profile Tab Enhancement Design Document ## Overview @@ -75,9 +77,9 @@ The Settings section will include: - Profile information management - Nostr account connection and management - Data synchronization preferences -- Privacy settings for social sharing +- Privacy settings for social sharing **we need to be careful with this as posting on public nostr relays is not inherently private. solutions for the future would be to allow the user to post to a personal private relay, or encrypt data in the future** - App preferences and customization -- Export and backup options +- Export and backup options ## Implementation Plan diff --git a/docs/doc-migrator.js b/docs/doc-migrator.js new file mode 100755 index 0000000..f20f9d7 --- /dev/null +++ b/docs/doc-migrator.js @@ -0,0 +1,113 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// Usage example: node doc-migrator.js docs/design/WorkoutCompletion.md docs/features/workout/completion_flow.md "Workout Completion Flow" "Active" + +// Parse command line arguments +const [source, destination, title, status] = process.argv.slice(2); + +if (!source || !destination || !title || !status) { + console.error('Usage: node doc-migrator.js <status>'); + process.exit(1); +} + +// Validate source file exists +if (!fs.existsSync(source)) { + console.error(`Source file not found: ${source}`); + process.exit(1); +} + +// Read source content +console.log(`Reading source file: ${source}`); +const sourceContent = fs.readFileSync(source, 'utf8'); + +// Create new content with template +const today = new Date().toISOString().slice(0, 10); +const newContent = `# ${title} + +**Last Updated:** ${today} +**Status:** ${status} +**Related To:** [Fill in related component] + +## Purpose + +[Brief description of this document's purpose] + +${sourceContent} + +## Related Documentation + +- [Add related document links] +`; + +// Ensure destination directory exists +const destDir = path.dirname(destination); +if (!fs.existsSync(destDir)) { + console.log(`Creating directory: ${destDir}`); + fs.mkdirSync(destDir, { recursive: true }); +} + +// Write new file +console.log(`Writing to destination: ${destination}`); +fs.writeFileSync(destination, newContent); +console.log(`Successfully migrated ${source} to ${destination}`); + +// Create a simple script to check for broken internal links +function createLinkChecker() { + const linkCheckerContent = `#!/usr/bin/env node +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const docsRoot = path.resolve(__dirname); + +// Get all markdown files +const allMdFiles = execSync(\`find \${docsRoot} -name "*.md"\`).toString().split('\\n').filter(Boolean); + +// Track all files and links +const allFiles = new Set(allMdFiles.map(f => path.relative(docsRoot, f))); +const brokenLinks = []; + +allMdFiles.forEach(file => { + const content = fs.readFileSync(file, 'utf8'); + const relativeFile = path.relative(docsRoot, file); + + // Find markdown links + const linkRegex = /\\[.*?\\]\\((.*?)\\)/g; + let match; + + while ((match = linkRegex.exec(content)) !== null) { + const link = match[1]; + + // Skip external links and anchors + if (link.startsWith('http') || link.startsWith('#')) continue; + + // Resolve relative to the file + const fileDir = path.dirname(relativeFile); + const resolvedLink = path.normalize(path.join(fileDir, link)); + + // Check if file exists + if (!allFiles.has(resolvedLink)) { + brokenLinks.push({ file: relativeFile, link, resolvedLink }); + } + } +}); + +if (brokenLinks.length > 0) { + console.log('Found broken links:'); + brokenLinks.forEach(({ file, link, resolvedLink }) => { + console.log(\`In \${file}: Broken link \${link} (resolves to \${resolvedLink})\`); + }); + process.exit(1); +} else { + console.log('All links are valid!'); +}`; + + fs.writeFileSync(path.join(__dirname, 'check-links.js'), linkCheckerContent); + console.log('Created link checker script: check-links.js'); +} + +// Create a link checker script +createLinkChecker(); diff --git a/docs/features/history/index.md b/docs/features/history/index.md new file mode 100644 index 0000000..90a4acc --- /dev/null +++ b/docs/features/history/index.md @@ -0,0 +1,129 @@ +# Workout History Features + +**Last Updated:** 2025-03-25 +**Status:** Active +**Related To:** Workout Features, Nostr Integration + +## Purpose + +This document describes the workout history features in the POWR app, focusing on the MVP implementation. It covers history views, data sources, and sharing capabilities. + +## Overview + +The workout history feature allows users to: + +1. View their past workout completions in chronological order +2. Review detailed workout data for completed sessions +3. Share workouts from history to Nostr +4. Access both local and Nostr-published workout records + +The history implementation is designed with the following principles: + +- Unified view of workouts from multiple sources +- Simple chronological listing +- Calendar-based frequency view +- Offline-first with sync capabilities +- Integration with social sharing + +## Component Architecture + +### High-Level Components + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ UI Layer │ │ Service Layer │ │ Data Layer │ +│ │ │ │ │ │ +│ History List │ │ History Service │ │ SQLite Storage │ +│ Calendar View │◄───►│ Sharing Service │◄───►│ Nostr Events │ +│ Detail Views │ │ Migration │ │ Caching │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +### UI Components + +- `WorkoutHistoryList`: Primary chronological list of completed workouts +- `CalendarView`: Basic calendar visualization of workout frequency +- `WorkoutDetailView`: Expanded view of a single workout record +- `WorkoutShareButton`: Allows sharing of completed workouts to Nostr + +### Service Layer + +- `UnifiedWorkoutHistoryService`: Combined access to multiple history sources +- `EnhancedWorkoutHistoryService`: Local workout history management +- `NostrWorkoutHistoryService`: Remote workout history via Nostr +- `PublicationQueueService`: Queue for publishing workout records to Nostr + +### Data Layer + +- `SQLite`: Local storage for workout history +- `EventCache`: Cached Nostr events for workout records + +## Implementation Details + +### Data Sources + +The workout history system supports multiple data sources: + +1. **Local SQLite Database** + - Primary storage for all workout records + - Complete history regardless of connectivity + - Stores workout details including exercises, sets, weights, etc. + +2. **Nostr Events (kind 1301)** + - Published workout records from the user + - Records from template authors for attribution + - Enables social sharing and discovery + +### History Views + +#### Chronological View +- Lists workouts by date, newest first +- Displays workout name, date, and basic summary +- Links to detailed workout view +- Includes sharing capability to publish to Nostr + +#### Calendar View +- Basic monthly calendar visualization +- Shows dates with completed workouts +- Simple tap to see workouts on a specific date + +#### Workout Details +- Complete set/rep/weight data +- Comparison to template (if used) +- Notes from workout +- Option to share to Nostr + +### Sharing to Nostr + +The history view includes the ability to share workouts to Nostr: + +1. User selects "Share" on any workout in history +2. User can add a note/caption +3. System publishes a kind 1301 workout record event +4. Optionally publishes a kind 1 note quoting the workout record + +This aligns with the MVP sharing functionality in the workout completion flow but extends it to previously completed workouts. + +### MVP Implementation Focus + +For the MVP release, the history tab focuses on: + +1. **Basic Workout History Display** + - Chronological list of workouts + - Simple calendar view + - Detailed workout view + +2. **Workout Sharing** + - Publishing kind 1301 workout records from history + - Enabling sharing of previously completed workouts + +3. **Unified History Sources** + - Showing both local and Nostr-published workouts + - Basic deduplication of records from multiple sources + +## Related Documentation + +- [Workout Completion Flow](../workout/completion_flow.md) - How workouts are recorded +- [Nostr Exercise NIP](../../technical/nostr/exercise_nip.md) - Protocol for workout data +- [MVP and Targeted Rebuild](../../project/mvp_and_rebuild.md) - Overall MVP strategy +- [Progress Tracking](../../features/profile/progress_tracking.md) - How progress is tracked in the profile tab diff --git a/docs/features/index.md b/docs/features/index.md new file mode 100644 index 0000000..c4950ba --- /dev/null +++ b/docs/features/index.md @@ -0,0 +1,32 @@ +# Feature Documentation + +This section contains documentation organized by app features. Each feature area has its own dedicated section with implementation details, architecture, and specifications. + +## Feature Areas + +- [Workout](./workout/index.md) - Workout creation, execution, and completion +- [Social](./social/index.md) - Social sharing and feed features +- [Library](./library/index.md) - Exercise and template library +- [POWR Packs](./powr_packs/index.md) - POWR pack import and management +- [Profile](./profile/index.md) - User profile and settings +- [History](./history/index.md) - Workout history and calendar +- [Settings](./settings/index.md) - App settings and configuration + +## MVP Features + +The following features are prioritized for the MVP release: + +- Core workout functionality (creation, execution, completion) +- Basic social sharing (simplified for MVP) +- Essential library management +- Profile functionality +- Streamlined POWR pack importing + +See [MVP and Targeted Rebuild](../project/mvp_and_rebuild.md) for more details on feature prioritization. + +## Related Documentation + +- [Architecture Documentation](../architecture/index.md) - System-wide architectural documentation +- [Technical Documentation](../technical/index.md) - Technical implementation details + +**Last Updated:** 2025-03-25 diff --git a/docs/features/powr_packs/implementation_plan.md b/docs/features/powr_packs/implementation_plan.md new file mode 100644 index 0000000..f6054e0 --- /dev/null +++ b/docs/features/powr_packs/implementation_plan.md @@ -0,0 +1,400 @@ +# POWR Pack Implementation Plan + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Workout Templates, Exercises, Nostr Integration + +## Purpose + +This document outlines the detailed implementation plan for the POWR Pack feature, focusing on both immediate technical solutions and longer-term architectural improvements. It serves as a guide for developers implementing and extending the feature. + +## Current Status Assessment + +Based on the current implementation of POWR Packs, several areas need attention: + +1. **Template-Exercise Relationships**: Templates are being imported but not properly linked to their associated exercises +2. **Parameter Extraction**: The system needs improvement in parsing parameters from exercise references +3. **Future Extensibility**: The current approach should support future changes to the NIP-4e specification +4. **Template Management**: Tools for template archiving and deletion need enhancement + +## Implementation Phases + +### Phase 1: Core Functionality (Implemented) + +The basic functionality of POWR Packs has been implemented, including: + +1. **Database Schema**: Tables to track imported packs and their contents +2. **POWRPackService**: Service for fetching packs from Nostr and importing them +3. **Import UI**: Interface for users to input `naddr1` links and select content to import +4. **Management UI**: Interface for viewing and deleting imported packs + +### Phase 2: Technical Enhancements + +#### Schema Extensions + +```sql +-- POWR Packs table +CREATE TABLE powr_packs ( + id TEXT PRIMARY KEY, + title TEXT NOT NULL, + description TEXT, + author_pubkey TEXT, + nostr_event_id TEXT, + import_date INTEGER NOT NULL +); + +-- POWR Pack items table +CREATE TABLE powr_pack_items ( + pack_id TEXT NOT NULL, + item_id TEXT NOT NULL, + item_type TEXT NOT NULL, + item_order INTEGER, + is_imported BOOLEAN NOT NULL DEFAULT 1, + PRIMARY KEY (pack_id, item_id), + FOREIGN KEY (pack_id) REFERENCES powr_packs(id) ON DELETE CASCADE +); + +-- Template extensions +ALTER TABLE templates ADD COLUMN is_archived BOOLEAN NOT NULL DEFAULT 0; +ALTER TABLE templates ADD COLUMN author_pubkey TEXT; +ALTER TABLE templates ADD COLUMN workout_type_config TEXT; + +-- Template exercise extensions +ALTER TABLE template_exercises ADD COLUMN params_json TEXT; +``` + +#### Template-Exercise Relationship Improvements + +The implementation needs to properly handle the relationship between templates and exercises: + +```typescript +// Find matching exercises based on Nostr references +function matchExerciseReferences(exercises: NDKEvent[], exerciseRefs: string[]): Map<string, string> { + const matchMap = new Map<string, string>(); + + for (const ref of exerciseRefs) { + // Extract the base reference (before any parameters) + const refParts = ref.split('::'); + const baseRef = refParts[0]; + + // Parse the reference format: kind:pubkey:d-tag + const refSegments = baseRef.split(':'); + if (refSegments.length < 3) continue; + + const refKind = refSegments[0]; + const refPubkey = refSegments[1]; + const refDTag = refSegments[2]; + + // Find the event that matches by d-tag + const matchingEvent = exercises.find(e => { + const dTag = findTagValue(e.tags, 'd'); + return dTag === refDTag && e.pubkey === refPubkey; + }); + + if (matchingEvent) { + matchMap.set(ref, matchingEvent.id); + } + } + + return matchMap; +} +``` + +#### Parameter Extraction + +The system needs to properly extract parameters from exercise references: + +```typescript +// Extract parameters from exercise reference +function extractParameters(exerciseRef: string): Record<string, any> { + const parameters: Record<string, any> = {}; + + // If no reference with parameters, return empty object + if (!exerciseRef || !exerciseRef.includes('::')) { + return parameters; + } + + const [baseRef, paramString] = exerciseRef.split('::'); + if (!paramString) return parameters; + + const paramValues = paramString.split(':'); + + // Map parameters to standard names + if (paramValues.length > 0) parameters.target_sets = parseInt(paramValues[0]) || null; + if (paramValues.length > 1) parameters.target_reps = parseInt(paramValues[1]) || null; + if (paramValues.length > 2) parameters.target_weight = parseFloat(paramValues[2]) || null; + if (paramValues.length > 3) parameters.set_type = paramValues[3]; + + return parameters; +} +``` + +#### Template Management + +Enhanced template management functions: + +```typescript +// Archive/unarchive a template +async function archiveTemplate(id: string, archive: boolean = true): Promise<void> { + await db.runAsync( + 'UPDATE templates SET is_archived = ? WHERE id = ?', + [archive ? 1 : 0, id] + ); +} + +// Remove a template from the library +async function removeTemplateFromLibrary(id: string): Promise<void> { + await db.withTransactionAsync(async () => { + // Delete template-exercise relationships + await db.runAsync( + 'DELETE FROM template_exercises WHERE template_id = ?', + [id] + ); + + // Delete template + await db.runAsync( + 'DELETE FROM templates WHERE id = ?', + [id] + ); + + // Update powr_pack_items to mark as not imported + await db.runAsync( + 'UPDATE powr_pack_items SET is_imported = 0 WHERE item_id = ? AND item_type = "template"', + [id] + ); + }); +} +``` + +### Phase 3: Extensibility Improvements + +To support future extensions to the NIP-4e specification, the implementation should include: + +#### 1. Flexible Parameter Handling + +Create a parameter mapper service that can dynamically handle different parameter formats: + +```typescript +class ExerciseParameterMapper { + // Extract parameters from a Nostr reference based on exercise format + static extractParameters(exerciseRef: string, formatJson?: string): Record<string, any> { + const parameters: Record<string, any> = {}; + + // If no reference with parameters, return empty object + if (!exerciseRef || !exerciseRef.includes('::')) { + return parameters; + } + + const [baseRef, paramString] = exerciseRef.split('::'); + if (!paramString) return parameters; + + const paramValues = paramString.split(':'); + + // If we have format information, use it to map parameters + if (formatJson) { + try { + const format = JSON.parse(formatJson); + const formatKeys = Object.keys(format).filter(key => format[key] === true); + + formatKeys.forEach((key, index) => { + if (index < paramValues.length && paramValues[index]) { + // Convert value to appropriate type based on parameter name + if (key === 'weight') { + parameters[key] = parseFloat(paramValues[index]) || null; + } else if (['reps', 'sets', 'duration'].includes(key)) { + parameters[key] = parseInt(paramValues[index]) || null; + } else { + // For other parameters, keep as string + parameters[key] = paramValues[index]; + } + } + }); + + return parameters; + } catch (error) { + console.warn('Error parsing format JSON:', error); + } + } + + // Default parameter mapping if no format or error parsing + if (paramValues.length > 0) parameters.target_sets = parseInt(paramValues[0]) || null; + if (paramValues.length > 1) parameters.target_reps = parseInt(paramValues[1]) || null; + if (paramValues.length > 2) parameters.target_weight = parseFloat(paramValues[2]) || null; + if (paramValues.length > 3) parameters.set_type = paramValues[3]; + + return parameters; + } +} +``` + +#### 2. Workout Type-Specific Handling + +Create processors for different workout types to handle their specific data needs: + +```typescript +// Interface for workout type processors +interface WorkoutTypeProcessor { + parseTemplateConfig(tags: string[][]): Record<string, any>; + getDefaultParameters(): Record<string, any>; + formatTemplateConfig(config: Record<string, any>): string[][]; +} + +// Factory for creating workout type processors +class WorkoutTypeFactory { + static createProcessor(type: string): WorkoutTypeProcessor { + switch (type) { + case 'strength': + return new StrengthWorkoutProcessor(); + case 'circuit': + return new CircuitWorkoutProcessor(); + case 'emom': + return new EMOMWorkoutProcessor(); + case 'amrap': + return new AMRAPWorkoutProcessor(); + default: + return new DefaultWorkoutProcessor(); + } + } +} +``` + +### Phase 4: Future Architecture + +For longer-term development, consider implementing: + +#### 1. Modular Event Processor Architecture + +```typescript +// Interface for event processors +interface NostrEventProcessor<T> { + // Check if processor can handle this event + canProcess(event: NostrEvent): boolean; + + // Process event to local model + processEvent(event: NostrEvent): T; + + // Convert local model to event + createEvent(model: T): NostrEvent; +} + +// Registry for event processors +class EventProcessorRegistry { + private processors: Map<number, NostrEventProcessor<any>[]> = new Map(); + + // Register a processor for a specific kind + registerProcessor(kind: number, processor: NostrEventProcessor<any>): void { + if (!this.processors.has(kind)) { + this.processors.set(kind, []); + } + + this.processors.get(kind)?.push(processor); + } + + // Get appropriate processor for an event + getProcessor<T>(event: NostrEvent): NostrEventProcessor<T> | null { + const kindProcessors = this.processors.get(event.kind); + if (!kindProcessors) return null; + + // Find the first processor that can process this event + for (const processor of kindProcessors) { + if (processor.canProcess(event)) { + return processor as NostrEventProcessor<T>; + } + } + + return null; + } +} +``` + +#### 2. Version-Aware Adapters + +```typescript +// Adapter for Nostr protocol versions +interface NostrProtocolAdapter { + // Get exercise from event + getExerciseFromEvent(event: NostrEvent): Exercise; + + // Get template from event + getTemplateFromEvent(event: NostrEvent): WorkoutTemplate; + + // Create events from local models + createExerciseEvent(exercise: Exercise): NostrEvent; + createTemplateEvent(template: WorkoutTemplate): NostrEvent; +} +``` + +## UI Components + +### Import Screen + +The import screen should include: + +1. Input field for `naddr1` links +2. Pack details display (title, description, author) +3. Selectable list of templates with thumbnails +4. Selectable list of exercises with auto-selection based on template dependencies +5. Import button with count of selected items + +### Management Screen + +The management screen should include: + +1. List of imported packs with: + - Pack title and description + - Author information with avatar + - Number of templates and exercises + - Import date + - Delete button with confirmation dialog + +### Social Discovery + +The social tab should include a POWR Packs section with: + +1. Horizontal scrolling list of available packs +2. Pack cards with: + - Pack title and thumbnail + - Author information + - Brief description + - "View Details" button + +## Testing Strategy + +### Unit Tests + +1. Test template-exercise relationship mapping +2. Test parameter extraction and formatting +3. Test template management functions +4. Test pack importing and deletion + +### Integration Tests + +1. Test end-to-end importing flow with mock Nostr events +2. Test dependency handling when selecting templates +3. Test social discovery functionality + +### User Acceptance Tests + +1. Test import flow with real Nostr packs +2. Test management interface with multiple imported packs +3. Test error handling with invalid `naddr1` links + +## Implementation Timeline + +1. **Phase 1 (Complete)**: Core functionality implementation +2. **Phase 2 (Weeks 1-2)**: Technical enhancements + - Fix template-exercise relationship + - Improve parameter extraction + - Enhance template management +3. **Phase 3 (Weeks 3-4)**: Extensibility improvements + - Implement flexible parameter handling + - Add workout type-specific processing +4. **Phase 4 (Future)**: Advanced architecture + - Implement modular event processor architecture + - Develop version-aware adapters + +## Related Documentation + +- [POWR Pack Overview](./overview.md) - Overview of the POWR Pack feature +- [Nostr Exercise NIP](../../technical/nostr/exercise_nip.md) - Nostr protocol specification for workout data +- [NDK Comprehensive Guide](../../technical/ndk/comprehensive_guide.md) - Guide to using the Nostr Development Kit diff --git a/docs/features/powr_packs/overview.md b/docs/features/powr_packs/overview.md new file mode 100644 index 0000000..5b2d4fa --- /dev/null +++ b/docs/features/powr_packs/overview.md @@ -0,0 +1,106 @@ +# POWR Pack Overview + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Workout Templates, Exercises, Nostr Integration + +## Purpose + +This document provides an overview of the POWR Pack feature, which allows users to import and manage collections of workout templates and exercises shared via the Nostr protocol. POWR Packs enable fitness content creators to share their workout programs with users in a decentralized manner. + +## Key Concepts + +### POWR Pack + +A POWR Pack is a collection of workout templates and exercises stored as a NIP-51 list (kind 30004 "Curation set") on the Nostr network. It enables: + +1. **Content Bundling**: Multiple workout templates and their required exercises grouped together +2. **Simple Sharing**: Distribution via `naddr1` links that encode references to the collection +3. **Decentralized Storage**: Content is hosted on the Nostr network, not centralized servers +4. **User Control**: Users can selectively import content they want from a pack + +### Core Functionality + +The POWR Pack feature provides the following capabilities: + +1. **Import Functionality**: Users can import packs via `naddr1` links +2. **Selective Import**: Users can choose which templates and exercises to import +3. **Dependency Management**: When selecting a template, required exercises are automatically selected +4. **Pack Management**: Users can view and delete imported packs + +## User Experience + +### Import Flow + +1. User accesses "Import POWR Pack" from the Settings menu +2. User enters or pastes an `naddr1` link +3. App fetches the pack content from Nostr relays +4. User selects which templates and exercises to import +5. Selected content is saved to the local database + +### Management Interface + +The "Manage POWR Packs" screen allows users to: +1. View all imported packs with metadata +2. See the number of templates and exercises in each pack +3. Delete packs while optionally keeping their content + +### Social Discovery + +The social tab includes a section for discovering POWR Packs: +1. Horizontal scrolling list of available packs +2. Pack preview with author and content summary +3. Tap to view and import content + +## Technical Implementation + +### Data Storage + +POWR Packs are tracked in two database tables: + +```sql +-- POWR Packs table +CREATE TABLE powr_packs ( + id TEXT PRIMARY KEY, + title TEXT NOT NULL, + description TEXT, + author_pubkey TEXT, + nostr_event_id TEXT, + import_date INTEGER NOT NULL +); + +-- POWR Pack items table +CREATE TABLE powr_pack_items ( + pack_id TEXT NOT NULL, + item_id TEXT NOT NULL, + item_type TEXT NOT NULL, + item_order INTEGER, + PRIMARY KEY (pack_id, item_id), + FOREIGN KEY (pack_id) REFERENCES powr_packs(id) ON DELETE CASCADE +); +``` + +### Nostr Integration + +POWR Packs leverage the Nostr protocol in several ways: + +1. **NIP-51 Lists**: Packs are stored as curated lists (kind 30004) +2. **NIP-19 Addresses**: Packs are shared via `naddr1` encoded addresses +3. **Custom Event Kinds**: Referenced content includes workout templates (kind 33402) and exercise templates (kind 33401) + +## Future Enhancements + +Potential future enhancements to the POWR Pack feature include: + +1. **User Profile Integration**: Filter or view all POWR Packs that a user subscribes to +2. **Creator Tools**: External website or app to create POWR Packs +3. **Expanded Import**: Support for importing individual workout templates or exercises via `naddr` or `nevent` references +4. **Dedicated Event Kind**: Create a special Nostr event kind for workout programming collections +5. **In-App Creation**: Allow users to create and publish their own POWR Packs + +## Related Documentation + +- [POWR Pack Implementation Plan](./implementation_plan.md) - Detailed implementation plan +- [Workout Templates](../workout/data_models.md) - Workout template data structures +- [Exercise Templates](../workout/data_models.md) - Exercise template data structures +- [Nostr Exercise NIP](../../technical/nostr/exercise_nip.md) - Nostr protocol specification for workout data diff --git a/docs/features/profile/index.md b/docs/features/profile/index.md new file mode 100644 index 0000000..ef4d0cb --- /dev/null +++ b/docs/features/profile/index.md @@ -0,0 +1,138 @@ +# Profile Features + +**Last Updated:** 2025-03-25 +**Status:** Active +**Related To:** User Identity, Progress, Settings + +## Purpose + +This document provides an overview of the profile tab features in the POWR app. It describes the various sections of the profile tab, their purposes, and how they integrate with other app features. + +## Overview + +The profile tab serves as the user's personal space within the app, providing: + +1. User identity and account management +2. Progress tracking and analytics +3. Application settings and preferences +4. Social activity overview +5. Account management features + +The profile implementation is focused on these key principles: + +- User control over personal information +- Clear organization of progress data +- Simple access to app settings +- Integration with Nostr for identity + +## Component Architecture + +### High-Level Components + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ UI Layer │ │ Service Layer │ │ Data Layer │ +│ │ │ │ │ │ +│ Profile Screens │ │ Profile Service │ │ User Data │ +│ Settings Views │◄───►│ Analytics │◄───►│ Settings Storage│ +│ Progress Views │ │ Auth Management │ │ Analytics Data │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +### Profile Screens + +- `ProfileOverview`: Main profile screen with user information +- `ProfileProgress`: Progress tracking visualizations and data +- `ProfileActivity`: Recent social and workout activity +- `ProfileTerms`: Terms of service and legal documents +- `ProfileSettings`: App settings and preferences + +### MVP Implementation Focus + +For the MVP release, the profile tab focuses on: + +1. **User Identity** + - Basic profile information display + - Profile editing capabilities + - Nostr pubkey association + +2. **Progress Tracking** + - Exercise progress charts and metrics + - Performance tracking over time + - Personal records and milestones + +3. **Core Settings** + - App preferences + - Theme switching + - Account management + +4. **Activity Overview** + - Limited view of recent workouts + - Social activity summary + - Simplified activity feed + +## User Identity + +The profile tab handles user identity through: + +1. **Profile Information** + - Display name + - Profile picture (with Nostr and local options) + - User metadata + - Exercise history summary + +2. **Authentication Management** + - Nostr key handling + - Login/logout functionality + - Key creation and import + +## Progress Features + +Progress tracking is a key feature of the profile tab: + +1. **Progress Charts** + - Exercise-specific progress tracking + - Weight/volume progression charts + - Performance metrics + - Personal records + +2. **Workout Summary Data** + - Total workouts completed + - Exercise frequency + - Workout time analytics + - Consistency metrics + +## Settings and Preferences + +The profile tab provides access to app settings: + +1. **App Preferences** + - Theme selection (light/dark) + - Notification preferences + - Default units (kg/lbs) + +2. **Account Management** + - Export/import data + - Clear cache + - Data management + +## Activity Overview + +The profile tab includes a simplified activity overview: + +1. **Recent Workouts** + - Last few completed workouts + - Quick stats + - Links to full history + +2. **Social Activity** + - Recent social interactions + - Posts and shares + - Simplified activity feed + +## Related Documentation + +- [Progress Tracking](./progress_tracking.md) - Detailed description of progress tracking features +- [MVP and Targeted Rebuild](../../project/mvp_and_rebuild.md) - Overall MVP strategy +- [Workout History](../history/index.md) - How workout history integrates with profile +- [Authentication](../../architecture/authentication.md) - Authentication architecture diff --git a/docs/features/profile/progress_tracking.md b/docs/features/profile/progress_tracking.md new file mode 100644 index 0000000..35772ae --- /dev/null +++ b/docs/features/profile/progress_tracking.md @@ -0,0 +1,127 @@ +# Progress Tracking + +**Last Updated:** 2025-03-25 +**Status:** Active +**Related To:** Profile Features, Analytics, Workout History + +## Purpose + +This document describes the progress tracking features in the POWR app's profile tab. It outlines how users can track their exercise progress, view trends over time, and analyze their workout performance. + +## Overview + +The progress tracking feature allows users to: + +1. View exercise-specific progress over time +2. Track key performance metrics (weight, volume, frequency) +3. Identify personal records and milestones +4. Analyze workout trends and patterns + +The progress tracking is designed with the following principles: + +- Exercise-focused rather than workout-focused +- Clear visual representation of progress +- Focus on actionable insights +- Privacy-first with user control over data sharing + +## Component Architecture + +### High-Level Components + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ UI Layer │ │ Service Layer │ │ Data Layer │ +│ │ │ │ │ │ +│ Progress Charts │ │ Analytics │ │ Workout History │ +│ Exercise List │◄───►│ Services │◄───►│ Data │ +│ Metrics Display │ │ Aggregation │ │ Calculation │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +### UI Components + +- `ProgressCharts`: Visual representations of key metrics over time +- `ExerciseList`: Selectable list of exercises with progress indicators +- `MetricsDisplay`: Numerical representation of key performance indicators +- `PRBadges`: Visual indicators of personal records + +### Service Layer + +- `AnalyticsService`: Processes raw workout data into progress metrics +- `WorkoutAggregator`: Combines data from multiple workouts for a given exercise +- `MetricsCalculator`: Computes derived metrics from raw workout data + +### Data Layer + +- Relies on `WorkoutHistoryService` for source data +- Cached calculations for performance +- Local-only analytics (no server processing) + +## Implementation Details + +### Key Metrics Tracked + +Progress tracking focuses on these primary metrics: + +1. **Weight Progression** + - One-rep max (calculated or actual) + - Working weight used for similar rep ranges + - Weight increases over time periods (week, month, year) + +2. **Volume Metrics** + - Total weight moved per exercise + - Sets × reps × weight calculations + - Volume trends over time + +3. **Frequency Analysis** + - Exercise frequency per time period + - Body part/movement pattern frequency + - Rest periods between similar workouts + +4. **Personal Records** + - Weight PRs at various rep ranges + - Volume PRs per session + - Streak and consistency records + +### Data Visualization + +Progress is visualized through: + +- Line charts for weight/volume progression +- Bar charts for frequency analysis +- Milestone markers for personal records +- Heat maps for workout frequency + +### Exercise Selection + +Users can track progress for: + +- Individual exercises (e.g., Barbell Bench Press) +- Exercise categories (e.g., all bench press variations) +- Movement patterns (e.g., all pushing movements) +- Body parts (e.g., all chest exercises) + +### Implementation Considerations + +Progress tracking presents several challenges addressed in the implementation: + +1. **Data Normalization** + - Handling similar exercises with different names + - Accounting for different equipment types + - Normalizing units (kg/lbs) + +2. **Valid Comparison** + - Comparing similar set/rep schemes + - Accounting for RPE/intensity differences + - Filtering anomalous data points + +3. **Performance Optimization** + - Pre-calculating common metrics + - Caching results for frequently viewed exercises + - Progressive loading of historical data + +## Related Documentation + +- [Workout History Features](../history/index.md) - Source data for progress tracking +- [MVP and Targeted Rebuild](../../project/mvp_and_rebuild.md) - Overall MVP strategy +- [Profile Tab Architecture](./profile_architecture.md) - Overall profile tab design diff --git a/docs/features/social/architecture.md b/docs/features/social/architecture.md new file mode 100644 index 0000000..9a85491 --- /dev/null +++ b/docs/features/social/architecture.md @@ -0,0 +1,278 @@ +# Social Architecture + +**Last Updated:** 2025-03-25 +**Status:** Active +**Related To:** Social Features, Nostr Integration + +## Purpose + +This document describes the overall architecture of the social features in the POWR app. It consolidates information from various social implementation documents, covering feed design, caching strategy, filtering rules, and MVP implementation. + +## Overview + +The POWR app social features enable users to: + +1. Share workout completions to Nostr +2. Follow other POWR users +3. View workout posts from followed users +4. Interact with global workout-related content +5. Maintain a profile with workout history and achievements + +The social architecture is designed with the following principles: + +- Privacy-first approach +- Offline-first with reliable sync +- Performance optimized for mobile +- Simplified MVP implementation +- Clear extension path for future features + +## Component Architecture + +### High-Level Components + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ UI Layer │ │ Service Layer │ │ Data Layer │ +│ │ │ │ │ │ +│ SocialPost │ │ SocialFeed │ │ NDK │ +│ FeedScreens │◄───►│ Services │◄───►│ EventCache │ +│ ProfileViews │ │ PublishQueue │ │ ContactCache │ +│ Interactions │ │ ProfileService │ │ ProfileCache │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +### UI Components + +- `SocialPost`: Displays individual posts with user info, content, and interactions +- `FeedScreens`: Three main feed views (Following, POWR, Global) +- `ProfileViews`: User profile display with posts and stats +- `EmptyFeed`: Placeholder for empty feed states +- `SocialOfflineState`: Indication and recovery for offline state + +### Service Layer + +- `SocialFeedService`: Feed management and aggregation +- `PublicationQueueService`: Queue for publishing events offline +- `ProfileService`: User profile management +- `ConnectivityService`: Network state monitoring +- `AuthenticationService`: Login state management +- `ContactCacheService`: Management of contact/following list + +### Data Layer + +- `NDK`: Nostr Development Kit for protocol interaction +- `EventCache`: Caching for Nostr events +- `ContactCache`: Caching for contact lists +- `ProfileCache`: Caching for user profiles + +## Feed Implementation + +### Feed Types + +1. **Following Feed**: Posts from users the current user follows +2. **POWR Feed**: Official POWR content and featured community posts +3. **Global Feed**: All workout-related content from the wider Nostr network + +### Feed Filtering Rules + +Feed content is filtered based on the following rules: + +1. **Content Types**: + - Workout records (kind 1301) + - Workout templates (kind 33401) + - Exercise definitions (kind 33402) + - Social posts referencing workouts (kind 1) + - Profile information (kind 0) + - Long-form content related to fitness (kind 30023) + - Highlight/achievement posts (kind 9734) + +2. **Following Feed**: + - Only from users in the user's contact list + - Must be workout-related (has workout reference or hashtag) + - Sorted by creation date descending + +3. **POWR Feed**: + - Content from official POWR account + - Content from featured users + - Trending workout posts based on interactions + - Educational content related to fitness + +4. **Global Feed**: + - All public workout-related posts + - Must have appropriate hashtags or workout references + - Filtered for relevance and quality + - Basic spam filtering applied + +### Feed Pagination + +Feeds implement cursor-based pagination with: +- Initial load of 20 items +- "Load more" triggers fetching next 20 items +- Event timestamps used as cursors + +## Caching Strategy + +### Event Caching + +Events are cached using a multi-tier approach: + +1. **Memory Cache**: + - Most recent events (last 24 hours) + - Limited to 1000 events per feed type + - LRU eviction policy + +2. **SQLite Persistent Cache**: + - Events stored in database for offline access + - Indexed by event ID and pubkey + - Automatic cleanup of older events (>30 days) + - Exception for user's own events (kept indefinitely) + +### Profile Caching + +User profiles are cached with: +- In-memory LRU cache for active browsing +- SQLite storage for persistent data +- TTL-based invalidation (refresh after 24 hours) +- Force refresh on explicit user action + +### Contact Caching + +Contact lists (users followed by the current user) are cached: +- In SQLite database via `ContactCacheService` +- Updated on initial login and periodically +- Contains pubkeys, relay hints, and petnames +- Used for Following feed filtering and contact display + +### Invalidation Strategy + +Cache invalidation occurs: +- On explicit pull-to-refresh +- When new events reference cached events +- After network reconnection +- When TTL expires + +## Authentication Flow + +``` +┌──────────┐ ┌───────────┐ ┌────────────┐ ┌───────────┐ +│ │ │ │ │ │ │ │ +│ Login │────►│ Generate/ │────►│ Initialize │────►│ Fetch │ +│ Prompt │ │ Load Keys │ │ NDK/Relays │ │ Profile │ +│ │ │ │ │ │ │ │ +└──────────┘ └───────────┘ └────────────┘ └───────────┘ + │ + ▼ + ┌───────────┐ + │ │ + │ Import │ + │ Contacts │ + │ │ + └───────────┘ +``` + +The authentication process: +1. User initiates login with npub or generates new keys +2. Keys securely stored in device secure storage +3. NDK initialized with signer and relays +4. Profile data fetched and cached +5. Contact list (kind 3 events) fetched and stored in ContactCache + - Contacts processed and stored in SQLite via ContactCacheService + - Includes pubkeys, relay hints, and petnames + - Used for Following feed population + - Periodically refreshed (every 6 hours of active use) + +## Publishing Flow + +``` +┌──────────┐ ┌───────────┐ ┌────────────┐ +│ │ │ │ │ │ +│ Create │────►│ Queue for │────►│ Publish to │ +│ Event │ │ Publish │ │ Relays │ +│ │ │ │ │ │ +└──────────┘ └───────────┘ └────────────┘ + │ ▲ + │ │ + ▼ │ + ┌───────────────┐ │ + │ Store in │ │ + │ Local Queue │───────────┘ + │ if Offline │ + └───────────────┘ +``` + +The publishing process handles: +1. Event creation with proper kind and tags +2. Offline queue management +3. Retry logic for failed publishes +4. Success/failure notifications +5. Local update before confirmation + +## MVP Implementation + +For the MVP release, we're implementing a simplified social experience: + +### MVP Social Features + +1. **Basic Publishing**: + - Workout sharing from completion flow + - Simple note composition + - Proper NIP-89 application attribution + +2. **Simplified Feed**: + - Official POWR feed content + - "Coming Soon" placeholder for Following/Global + - Basic post rendering without complex interactions + +3. **Essential Profile**: + - Basic profile display + - Workout history integration + - Minimal social activity display + +### MVP Technical Focus + +1. **Authentication Stability**: + - Reliable login/logout flow + - Proper state management + - Clear lifecycle hooks + +2. **Subscription Management**: + - Proper subscription cleanup + - Resource management + - Simplified subscription patterns + +3. **Offline Support**: + - Queue-based publishing + - Reliable local-first operation + - Simple sync indicators + +## Targeted Social Rebuild + +After the MVP release, we plan to implement a targeted rebuild of the social features: + +### Phase 1: Core Architecture Improvements + +1. Implement proper authentication state machine +2. Rebuild subscription management service +3. Create robust event cache service +4. Implement clean data/UI layer separation + +### Phase 2: Enhanced Social Features + +1. Robust Following feed with proper contact management +2. Interaction capabilities (likes, reposts) +3. Enhanced profile with proper metadata +4. Threading and replies for workouts + +### Phase 3: Advanced Features + +1. Search and discovery +2. Trends and analytics +3. Enhanced content filtering +4. Rich media embedding + +## Related Documentation + +- [MVP and Targeted Rebuild](../../project/mvp_and_rebuild.md) - MVP strategy details +- [NDK Comprehensive Guide](../../technical/ndk/comprehensive_guide.md) - NDK implementation +- [Exercise NIP Specification](../../technical/nostr/exercise_nip.md) - Nostr protocol for exercise data +- [Workout Completion Flow](../workout/completion_flow.md) - Integration with workout features diff --git a/docs/features/social/cache_implementation.md b/docs/features/social/cache_implementation.md new file mode 100644 index 0000000..645ccdf --- /dev/null +++ b/docs/features/social/cache_implementation.md @@ -0,0 +1,467 @@ +# Social Feed Cache Implementation + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Social Features, Data Layer, Performance + +## Purpose + +This document details the technical implementation of the caching system for the social feed in the POWR app. It describes the architecture, components, and mechanisms used to provide efficient data access, offline capabilities, and improved performance for social feed interactions. + +## Overview + +The cache system is designed to provide offline access to social feed data, reduce network usage, and improve performance. It employs several advanced techniques including write buffering, transaction locks, and in-memory caching. + +## Key Components + +1. **SocialFeedCache**: The main service that handles caching of social feed events +2. **EventCache**: A service for caching individual Nostr events +3. **useSocialFeed**: A hook that provides access to the social feed data +4. **RelayInitializer**: A component that initializes the cache system + +## Implementation Details + +### Write Buffer System + +The SocialFeedCache uses a write buffer system to batch database operations and reduce transaction conflicts. This approach is inspired by the Olas NDK Mobile implementation. + +```typescript +private writeBuffer: { query: string; params: any[] }[] = []; +private bufferFlushTimer: NodeJS.Timeout | null = null; +private bufferFlushTimeout: number = 100; // milliseconds +private processingTransaction: boolean = false; + +private bufferWrite(query: string, params: any[]) { + this.writeBuffer.push({ query, params }); + + if (!this.bufferFlushTimer) { + this.bufferFlushTimer = setTimeout(() => this.flushWriteBuffer(), this.bufferFlushTimeout); + } +} + +private async flushWriteBuffer() { + if (this.writeBuffer.length === 0 || this.processingTransaction) return; + + const bufferCopy = [...this.writeBuffer]; + this.writeBuffer = []; + + this.processingTransaction = true; + + try { + await this.db.withTransactionAsync(async () => { + for (const { query, params } of bufferCopy) { + await this.db.runAsync(query, params); + } + }); + } catch (error) { + console.error('[SocialFeedCache] Error flushing write buffer:', error); + // If there was an error, add the operations back to the buffer + for (const op of bufferCopy) { + if (!this.writeBuffer.some(item => + item.query === op.query && + JSON.stringify(item.params) === JSON.stringify(op.params) + )) { + this.writeBuffer.push(op); + } + } + } finally { + this.processingTransaction = false; + } + + this.bufferFlushTimer = null; + + // If there are more operations, start a new timer + if (this.writeBuffer.length > 0) { + this.bufferFlushTimer = setTimeout(() => this.flushWriteBuffer(), this.bufferFlushTimeout); + } +} +``` + +### In-Memory Tracking with LRU Cache + +To prevent redundant database operations, the SocialFeedCache uses an LRU (Least Recently Used) cache to track known events: + +```typescript +private knownEventIds: LRUCache<string, number>; // Event ID -> timestamp + +constructor(database: SQLiteDatabase) { + this.db = new DbService(database); + this.eventCache = new EventCache(database); + + // Initialize LRU cache for known events (limit to 1000 entries) + this.knownEventIds = new LRUCache<string, number>({ maxSize: 1000 }); + + // Ensure feed_cache table exists + this.initializeTable(); +} +``` + +### Debounced Subscriptions + +The `useSocialFeed` hook implements debouncing to prevent rapid resubscriptions: + +```typescript +// Subscription cooldown to prevent rapid resubscriptions +const subscriptionCooldown = useRef<NodeJS.Timeout | null>(null); +const cooldownPeriod = 2000; // 2 seconds +const subscriptionAttempts = useRef(0); +const maxSubscriptionAttempts = 3; + +// In loadFeed function: +// Prevent rapid resubscriptions +if (subscriptionCooldown.current) { + console.log('[useSocialFeed] Subscription on cooldown, skipping'); + return; +} + +// Track subscription attempts to prevent infinite loops +subscriptionAttempts.current += 1; +if (subscriptionAttempts.current > maxSubscriptionAttempts) { + console.error(`[useSocialFeed] Too many subscription attempts (${subscriptionAttempts.current}), giving up`); + setLoading(false); + return; +} + +// Set a cooldown to prevent rapid resubscriptions +subscriptionCooldown.current = setTimeout(() => { + subscriptionCooldown.current = null; + // Reset attempt counter after cooldown period + subscriptionAttempts.current = 0; +}, cooldownPeriod); +``` + +### Proper Initialization + +The RelayInitializer component ensures that the SocialFeedCache is properly initialized with the NDK instance: + +```typescript +// Initialize ProfileImageCache and SocialFeedCache with NDK instance +useEffect(() => { + if (ndk) { + console.log('[RelayInitializer] Setting NDK instance in ProfileImageCache'); + profileImageCache.setNDK(ndk); + + // Initialize SocialFeedCache with NDK instance + if (db) { + try { + const socialFeedCache = getSocialFeedCache(db); + socialFeedCache.setNDK(ndk); + console.log('[RelayInitializer] SocialFeedCache initialized with NDK'); + } catch (error) { + console.error('[RelayInitializer] Error initializing SocialFeedCache:', error); + } + } + } +}, [ndk, db]); +``` + +### Global Transaction Lock Mechanism + +To prevent transaction conflicts between different services (such as SocialFeedCache and ContactCacheService), we've implemented a global transaction lock mechanism in the SocialFeedCache class: + +```typescript +// Global transaction lock to prevent transaction conflicts across services +private static transactionLock: boolean = false; +private static transactionQueue: (() => Promise<void>)[] = []; +private static processingQueue: boolean = false; + +/** + * Acquire the global transaction lock + * @returns True if lock was acquired, false otherwise + */ +private static acquireTransactionLock(): boolean { + if (SocialFeedCache.transactionLock) { + return false; + } + SocialFeedCache.transactionLock = true; + return true; +} + +/** + * Release the global transaction lock + */ +private static releaseTransactionLock(): void { + SocialFeedCache.transactionLock = false; + // Process the next transaction in queue if any + if (SocialFeedCache.transactionQueue.length > 0 && !SocialFeedCache.processingQueue) { + SocialFeedCache.processTransactionQueue(); + } +} + +/** + * Add a transaction to the queue + * @param transaction Function that performs the transaction + */ +private static enqueueTransaction(transaction: () => Promise<void>): void { + SocialFeedCache.transactionQueue.push(transaction); + // Start processing the queue if not already processing + if (!SocialFeedCache.processingQueue) { + SocialFeedCache.processTransactionQueue(); + } +} + +/** + * Process the transaction queue + */ +private static async processTransactionQueue(): Promise<void> { + if (SocialFeedCache.processingQueue || SocialFeedCache.transactionQueue.length === 0) { + return; + } + + SocialFeedCache.processingQueue = true; + + try { + while (SocialFeedCache.transactionQueue.length > 0) { + // Wait until we can acquire the lock + if (!SocialFeedCache.acquireTransactionLock()) { + // If we can't acquire the lock, wait and try again + await new Promise(resolve => setTimeout(resolve, 100)); + continue; + } + + // Get the next transaction + const transaction = SocialFeedCache.transactionQueue.shift(); + if (!transaction) { + SocialFeedCache.releaseTransactionLock(); + continue; + } + + try { + // Execute the transaction + await transaction(); + } catch (error) { + console.error('[SocialFeedCache] Error executing queued transaction:', error); + } finally { + // Release the lock + SocialFeedCache.releaseTransactionLock(); + } + } + } finally { + SocialFeedCache.processingQueue = false; + } +} + +/** + * Execute a transaction with the global lock + * @param transaction Function that performs the transaction + */ +public static async executeWithLock(transaction: () => Promise<void>): Promise<void> { + // Add the transaction to the queue + SocialFeedCache.enqueueTransaction(transaction); +} +``` + +This mechanism ensures that only one transaction is active at any given time, preventing the "cannot start a transaction within a transaction" error that can occur when two services try to start transactions simultaneously. + +The `executeWithLock` method can be used by other services to coordinate their database transactions with SocialFeedCache: + +```typescript +// Example usage in ContactCacheService +async cacheContacts(ownerPubkey: string, contacts: string[]): Promise<void> { + if (!ownerPubkey || !contacts.length) return; + + try { + // Use the global transaction lock to prevent conflicts with other services + await SocialFeedCache.executeWithLock(async () => { + try { + // Use a transaction for better performance + await this.db.withTransactionAsync(async () => { + // Database operations... + }); + } catch (error) { + console.error('[ContactCacheService] Error in transaction:', error); + throw error; // Rethrow to ensure the transaction is marked as failed + } + }); + } catch (error) { + console.error('[ContactCacheService] Error caching contacts:', error); + } +} +``` + +### Enhanced Write Buffer System + +The write buffer system has been enhanced with exponential backoff and improved error handling: + +```typescript +private async flushWriteBuffer() { + if (this.writeBuffer.length === 0 || this.processingTransaction) return; + + // Check if database is available + if (!this.isDbAvailable()) { + console.log('[SocialFeedCache] Database not available, delaying flush'); + this.scheduleNextFlush(true); // Schedule with backoff + return; + } + + // Take only a batch of operations to process at once + const bufferCopy = [...this.writeBuffer].slice(0, this.maxBatchSize); + this.writeBuffer = this.writeBuffer.slice(bufferCopy.length); + + this.processingTransaction = true; + + // Use the transaction lock to prevent conflicts + try { + // Check if we've exceeded the maximum retry count + if (this.retryCount > this.maxRetryCount) { + console.warn(`[SocialFeedCache] Exceeded maximum retry count (${this.maxRetryCount}), dropping ${bufferCopy.length} operations`); + // Reset retry count but don't retry these operations + this.retryCount = 0; + this.processingTransaction = false; + this.scheduleNextFlush(); + return; + } + + // Increment retry count before attempting transaction + this.retryCount++; + + // Execute the transaction with the global lock + await SocialFeedCache.executeWithLock(async () => { + try { + // Execute the transaction + await this.db.withTransactionAsync(async () => { + for (const { query, params } of bufferCopy) { + try { + await this.db.runAsync(query, params); + } catch (innerError) { + // Log individual query errors but continue with other queries + console.error(`[SocialFeedCache] Error executing query: ${query}`, innerError); + // Don't rethrow to allow other queries to proceed + } + } + }); + + // Success - reset retry count + this.retryCount = 0; + this.dbAvailable = true; // Mark database as available + } catch (error) { + console.error('[SocialFeedCache] Error in transaction:', error); + + // Check for database connection errors + if (error instanceof Error && + (error.message.includes('closed resource') || + error.message.includes('Database not available'))) { + // Mark database as unavailable + this.dbAvailable = false; + console.warn('[SocialFeedCache] Database connection issue detected, marking as unavailable'); + + // Add all operations back to the buffer + this.writeBuffer = [...bufferCopy, ...this.writeBuffer]; + } else { + // For other errors, add operations back to the buffer + // but only if they're not already there (avoid duplicates) + for (const op of bufferCopy) { + if (!this.writeBuffer.some(item => + item.query === op.query && + JSON.stringify(item.params) === JSON.stringify(op.params) + )) { + // Add back to the beginning of the buffer to retry sooner + this.writeBuffer.unshift(op); + } + } + } + + // Rethrow to ensure the transaction is marked as failed + throw error; + } + }); + } catch (error) { + console.error('[SocialFeedCache] Error flushing write buffer:', error); + } finally { + this.processingTransaction = false; + this.scheduleNextFlush(); + } +} + +/** + * Schedule the next buffer flush with optional backoff + */ +private scheduleNextFlush(withBackoff: boolean = false) { + if (this.bufferFlushTimer) { + clearTimeout(this.bufferFlushTimer); + this.bufferFlushTimer = null; + } + + if (this.writeBuffer.length > 0) { + let delay = this.bufferFlushTimeout; + + if (withBackoff) { + // Use exponential backoff based on retry count + delay = Math.min( + this.bufferFlushTimeout * Math.pow(2, this.retryCount), + this.maxBackoffTime + ); + } + + console.log(`[SocialFeedCache] Scheduling next flush in ${delay}ms (retry: ${this.retryCount})`); + this.bufferFlushTimer = setTimeout(() => this.flushWriteBuffer(), delay); + } +} +``` + +## Benefits + +The cache implementation provides several key benefits: + +1. **Eliminated Transaction Conflicts**: The global transaction lock mechanism prevents transaction conflicts between different services. +2. **Improved Reliability**: The transaction queue ensures that all transactions are processed even if they can't be executed immediately. +3. **Enhanced Error Recovery**: The exponential backoff and retry mechanism improves recovery from temporary database errors. +4. **Better Offline Stability**: The system handles database unavailability gracefully, enabling seamless offline operation. +5. **Reduced Database Contention**: Coordinated transactions reduce contention on the database. +6. **Improved Performance**: The LRU cache reduces redundant database operations. +7. **Better Error Handling**: The system includes robust error handling to prevent cascading failures. +8. **Offline Support**: The cache system provides offline access to social feed data. +9. **Reduced Network Usage**: The system reduces network usage by caching events locally. + +## Debugging + +The Following screen includes debug information to help troubleshoot issues: + +```typescript +// Debug controls component - memoized +const DebugControls = useCallback(() => ( + <View className="bg-gray-100 p-4 rounded-lg mx-4 mb-4"> + <Text className="font-bold mb-2">Debug Info:</Text> + <Text>User: {currentUser?.pubkey?.substring(0, 8)}...</Text> + <Text>Feed Items: {entries.length}</Text> + <Text>Loading: {loading ? "Yes" : "No"}</Text> + <Text>Offline: {isOffline ? "Yes" : "No"}</Text> + <Text>Contacts: {contacts.length}</Text> + <Text>Loading Contacts: {isLoadingContacts ? "Yes" : "No"}</Text> + + <View className="flex-row mt-4 justify-between"> + <TouchableOpacity + className="bg-blue-500 p-2 rounded flex-1 mr-2" + onPress={checkRelayConnections} + > + <Text className="text-white text-center">Check Relays</Text> + </TouchableOpacity> + + <TouchableOpacity + className="bg-green-500 p-2 rounded flex-1" + onPress={handleRefresh} + > + <Text className="text-white text-center">Force Refresh</Text> + </TouchableOpacity> + </View> + </View> +), [currentUser?.pubkey, entries.length, loading, isOffline, contacts.length, isLoadingContacts, checkRelayConnections, handleRefresh]); +``` + +## Future Improvements + +Potential enhancements to the caching system include: + +1. **Automatic Cache Cleanup**: Implement automatic cleanup of old cached events. +2. **Cache Synchronization**: Implement synchronization between the cache and the server. +3. **Cache Compression**: Implement compression of cached data to reduce storage usage. +4. **Cache Encryption**: Implement encryption of cached data to improve security. +5. **Cache Analytics**: Implement analytics to track cache usage and performance. + +## Related Documentation + +- [Social Architecture](./architecture.md) - Overview of social features architecture +- [Feed Filtering](./feed_filtering.md) - Rules for filtering content in social feeds +- [Authentication](../../architecture/authentication.md) - User authentication flow +- [NDK Comprehensive Guide](../../technical/ndk/comprehensive_guide.md) - Reference for NDK implementation +- [Offline Queue](../../technical/nostr/offline_queue.md) - Implementation of offline publishing queue diff --git a/docs/features/social/feed_filtering.md b/docs/features/social/feed_filtering.md new file mode 100644 index 0000000..6748ee8 --- /dev/null +++ b/docs/features/social/feed_filtering.md @@ -0,0 +1,233 @@ +# Social Feed Filtering Rules + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Social Features, Feed Implementation + +## Purpose + +This document defines the filtering rules for different social feed tabs in the POWR app, providing implementation details on how content is filtered and displayed to users based on feed type. + +## Overview + +The POWR app has three main social feed tabs: +1. **POWR** - Official content from the POWR team +2. **Following** - Content from users the current user follows +3. **Community** (formerly Global) - Content from the broader Nostr community + +Each feed has specific filtering rules to ensure users see relevant fitness-related content. + +## Content Types + +The app handles several types of Nostr events: +- **Social Posts** (kind 1) - Regular text posts +- **Articles** (kind 30023) - Long-form content +- **Article Drafts** (kind 30024) - Unpublished long-form content +- **Workout Records** (kind 1301) - Completed workouts +- **Exercise Templates** (kind 33401) - Exercise definitions +- **Workout Templates** (kind 33402) - Workout plans + +## Filtering Rules + +### POWR Feed +- Shows content **only** from the official POWR account (`npub1p0wer69rpkraqs02l5v8rutagfh6g9wxn2dgytkv44ysz7avt8nsusvpjk`) +- Includes: + - Social posts (kind 1) + - Published articles (kind 30023) + - Workout records (kind 1301) + - Exercise templates (kind 33401) + - Workout templates (kind 33402) +- **Excludes** article drafts (kind 30024) + +### Following Feed +- Shows content from users the current user follows +- For social posts (kind 1) and articles (kind 30023), only shows content with fitness-related tags: + - #workout + - #fitness + - #powr + - #31days + - #crossfit + - #wod + - #gym + - #strength + - #cardio + - #training + - #exercise +- Always shows workout-specific content (kinds 1301, 33401, 33402) from followed users +- **Excludes** article drafts (kind 30024) + +### Community Feed +- Shows content from all users +- For social posts (kind 1) and articles (kind 30023), only shows content with fitness-related tags (same as Following Feed) +- Always shows workout-specific content (kinds 1301, 33401, 33402) +- **Excludes** article drafts (kind 30024) + +### User Activity Feed +- Shows only the current user's own content +- For social posts (kind 1) and articles (kind 30023), only shows content with fitness-related tags (same as Following Feed) +- Always shows the user's workout-specific content (kinds 1301, 33401, 33402) +- **Excludes** article drafts (kind 30024) + +## Implementation Details + +The filtering is implemented in several key files: +- `lib/social/socialFeedService.ts` - Core service that handles feed subscriptions +- `lib/hooks/useFeedHooks.ts` - React hooks for the different feed types +- `components/social/EnhancedSocialPost.tsx` - Component that renders feed items + +### Tag-Based Filtering + +For social posts and articles, we filter based on the presence of fitness-related tags. This ensures that users only see content relevant to fitness and workouts. + +### Content Type Filtering + +Workout-specific content (kinds 1301, 33401, 33402) is always included in the feeds, as these are inherently fitness-related. + +### Draft Exclusion + +Article drafts (kind 30024) are excluded from all feeds to ensure users only see published content. + +## Modifying Feed Filtering + +If you need to modify the event types or tags used for filtering, you'll need to update the following files: + +### 1. To modify event kinds (content types): + +#### a. `lib/social/socialFeedService.ts`: +- The `subscribeFeed` method contains the core filtering logic +- Modify the `workoutFilter` object to change workout-specific content kinds (1301, 33401, 33402) +- Modify the `socialPostFilter` object to change social post kinds (1) +- Modify the `articleFilter` object to change article kinds (30023) +- The special case for draft articles (30024) has been removed, but you can add it back if needed + +```typescript +// Example: To add a new workout-related kind (e.g., 1302) +const workoutFilter: NDKFilter = { + kinds: [1301, 33401, 33402, 1302] as any[], + // ... +}; +``` + +#### b. `lib/hooks/useFeedHooks.ts`: +- Update the filter arrays in each hook function: + - `useFollowingFeed` + - `usePOWRFeed` + - `useGlobalFeed` + - `useUserActivityFeed` + +```typescript +// Example: Adding a new kind to the POWR feed +const powrFilters = useMemo<NDKFilter[]>(() => { + if (!POWR_PUBKEY_HEX) return []; + + return [ + { + kinds: [1, 30023, 1302] as any[], // Added new kind 1302 + authors: [POWR_PUBKEY_HEX], + limit: 25 + }, + // ... + ]; +}, []); +``` + +### 2. To modify fitness-related tags: + +#### a. `lib/social/socialFeedService.ts`: +- Find the tag arrays in the `socialPostFilter` and `articleFilter` objects: + +```typescript +socialPostFilter['#t'] = [ + 'workout', 'fitness', 'powr', '31days', + 'crossfit', 'wod', 'gym', 'strength', + 'cardio', 'training', 'exercise' + // Add new tags here +]; +``` + +#### b. `lib/hooks/useFeedHooks.ts`: +- Update the tag arrays in each hook function: + - `useFollowingFeed` + - `useGlobalFeed` + - `useUserActivityFeed` + +```typescript +'#t': [ + 'workout', 'fitness', 'powr', '31days', + 'crossfit', 'wod', 'gym', 'strength', + 'cardio', 'training', 'exercise', + 'newTag1', 'newTag2' // Add new tags here +] +``` + +### 3. To modify content rendering: + +#### a. `components/social/EnhancedSocialPost.tsx`: +- The `renderContent` method determines how different content types are displayed +- Modify this method if you add new event kinds or need to change how existing kinds are rendered + +```typescript +// Example: Adding support for a new kind +case 'newContentType': + return <NewContentTypeComponent data={item.parsedContent as NewContentType} />; +``` + +### 4. To modify event parsing: + +#### a. `lib/hooks/useSocialFeed.ts`: +- The `processEvent` function parses events based on their kind +- Update this function if you add new event kinds or change how existing kinds are processed + +```typescript +// Example: Adding support for a new kind +case NEW_KIND: + feedItem = { + id: event.id, + type: 'newType', + originalEvent: event, + parsedContent: parseNewContent(event), + createdAt: timestamp + }; + break; +``` + +### 5. Event type definitions: + +#### a. `types/nostr-workout.ts`: +- Contains the `POWR_EVENT_KINDS` enum with all supported event kinds +- Update this enum if you add new event kinds + +```typescript +// Example: Adding a new kind +export enum POWR_EVENT_KINDS { + // Existing kinds... + NEW_KIND = 1302, +} +``` + +## Testing Changes + +After modifying the filtering rules, test the changes in all feed tabs: +1. POWR feed +2. Following feed +3. Community feed +4. User Activity feed (in the Profile tab) + +Verify that: +- Only the expected content types appear in each feed +- Content with the specified tags is properly filtered +- New event kinds are correctly rendered + +## Future Improvements + +Potential future improvements to the filtering system: +- Add user-configurable filters for specific fitness interests +- Implement AI-based content relevance scoring +- Add support for more content types as the Nostr ecosystem evolves + +## Related Documentation + +- [Social Architecture](./architecture.md) - Overall social features architecture +- [Authentication](../../architecture/authentication.md) - User authentication flow +- [NDK Comprehensive Guide](../../technical/ndk/comprehensive_guide.md) - Reference for NDK implementation +- [Nostr Exercise NIP](../../technical/nostr/exercise_nip.md) - Technical specification for workout event format diff --git a/docs/features/social/feed_implementation_details.md b/docs/features/social/feed_implementation_details.md new file mode 100644 index 0000000..bba0eb3 --- /dev/null +++ b/docs/features/social/feed_implementation_details.md @@ -0,0 +1,564 @@ +# Social Feed Implementation Details + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Social Features, NDK Integration, Feed Implementation + +## Purpose + +This document provides detailed technical implementation specifications for the social feed in the POWR app, including data flow architecture, event structures, component implementations, and integration points with the existing codebase. + +## Technical Considerations + +### Data Flow + +The data flow for the social feed will leverage your existing NDK integration: + +1. **Subscription Management** + - Use your existing `useSubscribe` hook with appropriate filters for different feed types + - Implement efficient subscription handling to minimize relay connections + - Use NDK's subscription grouping for better relay performance + +2. **Event Processing** + - Parse Nostr events into app-specific data structures using the parser functions + - Integrate with your existing workout data models + - Handle event validation and error cases + +3. **UI Rendering** + - Use React Native's `FlatList` for efficient rendering of feed items + - Implement proper list virtualization for performance + - Use memoization to prevent unnecessary re-renders + +### NDK Outbox Model + +Your `initNDK.ts` already configures the outbox model. This is important for optimizing event delivery: + +1. **Relay Management** + - The outbox model helps route events to the most appropriate relays + - Configure relay preferences based on event types and content + - Optimize relay selection for event delivery + +2. **Event Publishing** + - Use the outbox model to ensure events reach appropriate relays + - Configure fallback relays for critical events + - Monitor delivery status using NDK's built-in mechanisms + +### Offline Support + +For true offline support: + +1. **Local Event Storage** + - Implement a pending events table in your SQLite database + - Store unsent events when offline + - Provide retry logic for failed publications + +2. **UI Indicators** + - Show visual indicators for pending publications + - Implement status tracking for shared content + - Allow users to manually retry failed shares + +### Performance Optimizations + +1. **Feed Rendering** + - Use windowed lists for performance + - Implement paged loading for long feeds + - Cache rendered components to minimize re-renders + +2. **Profile Caching** + - Cache user profiles to reduce relay requests + - Implement TTL-based caching for profile data + - Prefetch profiles for visible feed items + +3. **Media Handling** + - Implement lazy loading for images + - Use proper caching for media content + - Consider progressive loading for large media + +## Event Type Structures + +### Workout Record (kind: 1301) +```json +{ + "kind": 1301, + "content": "<workout notes>", + "tags": [ + ["d", "<UUID>"], + ["title", "<workout name>"], + ["type", "<workout type>"], + ["rounds_completed", "<number of rounds completed>"], + ["start", "<Unix timestamp in seconds>"], + ["end", "<Unix timestamp in seconds>"], + ["exercise", "<kind>:<pubkey>:<d-tag>", "<relay-url>", "<weight>", "<reps>", "<rpe>", "<set_type>"], + ["exercise", "<kind>:<pubkey>:<d-tag>", "<relay-url>", "<weight>", "<reps>", "<rpe>", "<set_type>"], + ["template", "<kind>:<pubkey>:<d-tag>", "<relay-url>"], + ["pr", "<kind>:<pubkey>:<d-tag>,<metric>,<value>"], + ["completed", "<true/false>"], + ["t", "<hashtag>"], + ["t", "<hashtag>"] + ] +} +``` + +### Exercise Template (kind: 33401) +```json +{ + "kind": 33401, + "content": "<detailed form instructions and notes>", + "tags": [ + ["d", "<UUID>"], + ["title", "<exercise name>"], + ["format", "<parameter>", "<parameter>", "<parameter>", "<parameter>"], + ["format_units", "<unit>", "<unit>", "<unit>", "<unit>"], + ["equipment", "<equipment type>"], + ["difficulty", "<skill level>"], + ["imeta", + "url <url to demonstration media>", + "m <media type>", + "dim <dimensions>", + "alt <alt text>" + ], + ["t", "<hashtag>"], + ["t", "<hashtag>"] + ] +} +``` + +### Workout Template (kind: 33402) +```json +{ + "kind": 33402, + "content": "<workout notes and instructions>", + "tags": [ + ["d", "<UUID>"], + ["title", "<workout name>"], + ["type", "<workout type>"], + ["rounds", "<number of rounds>"], + ["duration", "<duration in seconds>"], + ["interval", "<interval in seconds>"], + ["rest_between_rounds", "<rest time in seconds>"], + ["exercise", "<kind>:<pubkey>:<d-tag>", "<relay-url>", "<param1>", "<param2>", "<param3>", "<param4>"], + ["exercise", "<kind>:<pubkey>:<d-tag>", "<relay-url>", "<param1>", "<param2>", "<param3>", "<param4>"], + ["t", "<hashtag>"], + ["t", "<hashtag>"] + ] +} +``` + +## Integration with Existing Types + +To ensure compatibility with your existing codebase, we'll need to extend your current types: + +```typescript +// Add to types/nostr.ts +export interface NostrWorkoutRecord extends NostrEvent { + kind: NostrEventKind.WORKOUT; + tags: string[][]; +} + +export interface NostrExerciseTemplate extends NostrEvent { + kind: NostrEventKind.EXERCISE; + tags: string[][]; +} + +export interface NostrWorkoutTemplate extends NostrEvent { + kind: NostrEventKind.TEMPLATE; + tags: string[][]; +} + +// Interface for parsed workout record +export interface ParsedWorkoutRecord { + id: string; + title: string; + type: string; + startTime: number; + endTime: number; + completed: boolean; + exercises: ParsedExercise[]; + templateReference?: string; + notes: string; +} + +// Interface for parsed exercise +export interface ParsedExercise { + id: string; + name: string; + weight?: number; + reps?: number; + rpe?: number; + setType: string; +} +``` + +## Code Examples + +### Enhanced NDK Integration + +```typescript +// Enhancement to useSubscribe.ts to support workout-specific events +export function useWorkoutFeed( + feedType: 'following' | 'powr' | 'global' +) { + const { ndk } = useNDK(); + const { currentUser } = useNDKCurrentUser(); + const [feedItems, setFeedItems] = useState<NDKEvent[]>([]); + + // Define filters based on your existing pattern but with workout event kinds + const getFilters = useCallback(() => { + const baseFilters = [{ + kinds: [1, 1301, 33401, 33402], // Notes, Workouts, Exercise Templates, Workout Templates + limit: 20 + }]; + + // Customize based on feed type + switch (feedType) { + case 'following': + // Use your existing pattern for following users + return baseFilters.map(filter => ({ + ...filter, + // Add authors filter based on who the user follows + '#p': currentUser?.follows || [] + })); + case 'powr': + return baseFilters.map(filter => ({ + ...filter, + authors: ['npub1p0wer69rpkraqs02l5v8rutagfh6g9wxn2dgytkv44ysz7avt8nsusvpjk'] + })); + default: + return baseFilters; + } + }, [feedType, currentUser]); + + // Use your existing useSubscribe hook + const { events, isLoading, eose, resubscribe } = useSubscribe( + getFilters(), + { enabled: !!ndk } + ); + + // Process events into feed items + useEffect(() => { + if (events.length) { + // Convert NDK events to feed items with proper parsing + const processedItems = events.map(event => { + switch (event.kind) { + case 1301: // Workout records + return processWorkoutEvent(event); + case 33401: // Exercise templates + return processExerciseTemplateEvent(event); + case 33402: // Workout templates + return processWorkoutTemplateEvent(event); + default: // Standard posts + return processStandardPost(event); + } + }); + + setFeedItems(processedItems); + } + }, [events]); + + return { + feedItems, + isLoading, + refreshFeed: resubscribe + }; +} +``` + +### Event Type Parsers + +```typescript +// Add to utils/nostr-utils.ts +export function processWorkoutEvent(event: NDKEvent): WorkoutFeedItem { + const title = findTagValue(event.tags, 'title') || 'Workout'; + const type = findTagValue(event.tags, 'type') || 'strength'; + const startTime = parseInt(findTagValue(event.tags, 'start') || '0'); + const endTime = parseInt(findTagValue(event.tags, 'end') || '0'); + + // Extract exercises from tags + const exercises = event.tags + .filter(tag => tag[0] === 'exercise') + .map(tag => parseExerciseTag(tag)); + + // Map to your existing feed item structure + return { + id: event.id, + type: 'workout', + author: event.pubkey, + createdAt: event.created_at, + content: event.content, + title, + workoutType: type, + duration: endTime - startTime, + exercises, + // Map other properties as needed + }; +} + +export function parseExerciseTag(tag: string[]): ExerciseData { + // Format: ['exercise', '<kind>:<pubkey>:<d-tag>', '<relay-url>', '<weight>', '<reps>', '<rpe>', '<set_type>'] + if (tag.length < 7) { + // Handle incomplete tags + return { + id: tag[1] || '', + name: 'Unknown Exercise', + weight: null, + reps: null, + rpe: null, + setType: 'normal' + }; + } + + // Extract exercise ID parts (kind:pubkey:d-tag) + const idParts = tag[1].split(':'); + const exerciseId = idParts.length > 2 ? idParts[2] : tag[1]; + + return { + id: exerciseId, + name: exerciseId, // Placeholder - should be resolved from your exercise database + weight: tag[3] ? parseFloat(tag[3]) : null, + reps: tag[4] ? parseInt(tag[4]) : null, + rpe: tag[5] ? parseFloat(tag[5]) : null, + setType: tag[6] || 'normal' + }; +} + +export function processExerciseTemplateEvent(event: NDKEvent): ExerciseFeedItem { + const title = findTagValue(event.tags, 'title') || 'Exercise'; + const equipment = findTagValue(event.tags, 'equipment'); + const difficulty = findTagValue(event.tags, 'difficulty'); + + // Parse format data + const formatTag = event.tags.find(tag => tag[0] === 'format'); + const formatUnitsTag = event.tags.find(tag => tag[0] === 'format_units'); + + // Get tags for categorization + const categories = event.tags + .filter(tag => tag[0] === 't') + .map(tag => tag[1]); + + // Extract media if present + const mediaTag = event.tags.find(tag => tag[0] === 'imeta'); + let media = null; + + if (mediaTag && mediaTag.length > 1) { + const urlPart = mediaTag[1].split(' '); + if (urlPart.length > 1) { + media = { + url: urlPart[1], + mimeType: mediaTag[2]?.split(' ')[1] || '', + altText: mediaTag[4]?.substring(4) || '' + }; + } + } + + return { + id: event.id, + type: 'exerciseTemplate', + author: event.pubkey, + createdAt: event.created_at, + content: event.content, + title, + equipment, + difficulty, + categories, + media, + // Map other properties as needed + }; +} +``` + +### Enhanced Social Post Component + +```tsx +// Enhancement to components/social/SocialPost.tsx +interface WorkoutPostProps { + event: NDKEvent; + parsed: WorkoutFeedItem; +} + +function WorkoutPost({ event, parsed }: WorkoutPostProps) { + return ( + <Card className="mb-4"> + <CardHeader className="pb-2"> + <UserAvatar pubkey={event.pubkey} /> + <View> + <Text className="font-semibold">{parsed.displayName || 'Athlete'}</Text> + <Text className="text-xs text-muted-foreground"> + completed a {parsed.workoutType} workout • {timeAgo(parsed.createdAt)} + </Text> + </View> + </CardHeader> + + <CardContent className="py-2"> + <Text className="font-medium text-lg mb-1">{parsed.title}</Text> + + {parsed.exercises.length > 0 && ( + <View className="mt-2"> + <Text className="font-medium mb-1">Exercises:</Text> + {parsed.exercises.slice(0, 3).map((exercise, index) => ( + <Text key={index} className="text-sm"> + • {exercise.name} {exercise.weight ? `${exercise.weight}kg` : ''} + {exercise.reps ? ` × ${exercise.reps}` : ''} + </Text> + ))} + {parsed.exercises.length > 3 && ( + <Text className="text-sm text-muted-foreground"> + +{parsed.exercises.length - 3} more exercises + </Text> + )} + </View> + )} + + {parsed.content && ( + <Text className="mt-2">{parsed.content}</Text> + )} + </CardContent> + + <CardFooter className="pt-1"> + <InteractionButtons event={event} /> + </CardFooter> + </Card> + ); +} + +// Enhanced SocialPost component +export default function SocialPost({ event }: { event: NDKEvent }) { + // Parse event based on kind + const parsed = useMemo(() => { + switch (event.kind) { + case 1301: + return processWorkoutEvent(event); + case 33401: + return processExerciseTemplateEvent(event); + case 33402: + return processWorkoutTemplateEvent(event); + default: + return processStandardPost(event); + } + }, [event]); + + // Render different components based on event kind + switch (event.kind) { + case 1301: + return <WorkoutPost event={event} parsed={parsed} />; + case 33401: + return <ExerciseTemplatePost event={event} parsed={parsed} />; + case 33402: + return <WorkoutTemplatePost event={event} parsed={parsed} />; + default: + return <StandardPost event={event} parsed={parsed} />; + } +} +``` + +### Workout Sharing Integration + +```tsx +// Enhancement to components/workout/WorkoutCompletionFlow.tsx +// Add to your existing implementation +const handleShareWorkout = async () => { + if (!ndk || !currentUser || !workout) return; + + // Create a workout record event + const workoutEvent = new NDKEvent(ndk); + workoutEvent.kind = 1301; // Workout Record + workoutEvent.content = notes || ''; + + // Add tags based on completed workout + const tags = [ + ['d', workout.id], // Use your UUID + ['title', workout.title], + ['type', workout.type], + ['start', workout.startTime.toString()], + ['end', workout.endTime.toString()], + ['completed', 'true'] + ]; + + // Add exercise tags + workout.exercises.forEach(exercise => { + // Format: exercise, reference, relay, weight, reps, rpe, set_type + tags.push([ + 'exercise', + `33401:${exercise.id}`, + '', + exercise.weight?.toString() || '', + exercise.reps?.toString() || '', + exercise.rpe?.toString() || '', + 'normal' + ]); + }); + + // Add template reference if used + if (workout.templateId) { + tags.push(['template', `33402:${workout.templateId}`, '']); + } + + // Add hashtags + tags.push(['t', 'workout']); + tags.push(['t', 'powrapp']); + + workoutEvent.tags = tags; + + try { + await workoutEvent.publish(); + // Show success message + showToast('Workout shared successfully!'); + } catch (error) { + console.error('Error sharing workout:', error); + showToast('Failed to share workout'); + } +}; +``` + +## Security Considerations + +1. **Data Privacy** + - Allow users to control which workouts are shared + - Provide clear privacy indicators + - Support event deletion (NIP-09) + +2. **Content Moderation** + - Implement user blocking + - Add reporting mechanisms + - Support muting functionality + +3. **User Safety** + - Protect sensitive health data + - Allow selective sharing + - Provide education on privacy implications + +## Implementation Roadmap + +### Phase 1: Enhanced NDK Integration (2-3 weeks) +- Complete NDK integration with proper caching +- Implement event parsers for workout-specific event kinds +- Enhance existing subscription mechanisms + +### Phase 2: Core Social Feed Components (3-4 weeks) +- Enhance SocialPost component for different event types +- Implement detailed WorkoutPost, TemplatePost, and ExercisePost components +- Add interaction components (likes, comments, reposts) + +### Phase 3: Workout Sharing Functionality (2-3 weeks) +- Integrate with existing workout completion flow +- Implement bidirectional sync between local and Nostr data +- Add ability to share templates and exercises + +### Phase 4: Detail Screens & Advanced Features (3-4 weeks) +- Create detail screens for different content types +- Implement comments and replies functionality +- Add profile enhancements to show user's content + +### Phase 5: Polish & Optimization (2 weeks) +- Optimize performance for large feeds +- Enhance offline support +- Add media handling for workout photos +- Implement pull-to-refresh and infinite scrolling + +## Related Documentation + +- [Social Architecture](./architecture.md) - Overall social features architecture +- [Feed Filtering](./feed_filtering.md) - Rules for filtering content in social feeds +- [Cache Implementation](./cache_implementation.md) - Details on the social feed caching system +- [Implementation Plan](./implementation_plan.md) - High-level implementation plan +- [NDK Comprehensive Guide](../../technical/ndk/comprehensive_guide.md) - Reference for NDK implementation +- [Nostr Exercise NIP](../../technical/nostr/exercise_nip.md) - Technical specification for workout event format diff --git a/docs/features/social/implementation_plan.md b/docs/features/social/implementation_plan.md new file mode 100644 index 0000000..af1250d --- /dev/null +++ b/docs/features/social/implementation_plan.md @@ -0,0 +1,288 @@ +# Social Feed Implementation Plan + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Social Features, NDK Integration, Feed Architecture + +## Purpose + +This document outlines the technical implementation plan for improving the social feed in the POWR app. It analyzes the best practices from both NDK and Olas implementations, providing a detailed roadmap for refactoring the current social feed architecture to be more performant, reliable, and maintainable. + +## Implementation Strategy + +### 1. Implement a Centralized Feed Management System + +Based on both Olas and NDK's approaches: + +- **Create a `FeedEntry` type** with clear support for different event kinds and content types +- **Implement state management using references** following NDK's pattern of using `Map` and `Set` references for better performance +- **Add proper event deduplication** using NDK's seen events tracking mechanism + +### 2. Improve Event Subscription and Filtering + +From NDK's implementation: + +- **Utilize NDK's subscription options more effectively**: + ```typescript + const subscription = ndk.subscribe(filters, { + closeOnEose: true, + cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST, + groupable: true, + skipVerification: false + }); + ``` + +- **Implement caching strategies** using NDK's caching mechanisms: + ```typescript + // Try cache first, then relays + const cacheStrategy = NDKSubscriptionCacheUsage.CACHE_FIRST; + ``` + +- **Use subscription grouping** to reduce relay connections for similar queries: + ```typescript + // Group similar subscriptions with a delay + const groupableDelay = 100; // ms + ``` + +### 3. Enhance Contact List Fetching + +NDK offers more sophisticated contact list handling: + +- **Use NDK's contact list event fetching** with explicit validation: + ```typescript + // Direct method to get follows for a user + async followSet(opts?: NDKSubscriptionOptions): Promise<Set<Hexpubkey>> { + const follows = await this.follows(opts); + return new Set(Array.from(follows).map((f) => f.pubkey)); + } + ``` + +- **Implement loading states and retry logic** using NDK's pattern: + ```typescript + // Track loading state with cleanup on unmount + let isMounted = true; + // ... + return () => { isMounted = false; } + ``` + +### 4. Enhanced Subscription Management + +From NDK's subscription implementation: + +- **Proper lifecycle management** for subscriptions: + ```typescript + // Keep track of subscriptions for cleanup + const subscriptionRef = useRef<NDKSubscription | null>(null); + + // Clean up subscription on component unmount + useEffect(() => { + return () => { + if (subscriptionRef.current) { + subscriptionRef.current.stop(); + } + }; + }, []); + ``` + +- **Handle relay connection state** more effectively: + ```typescript + // Monitor relay connections + ndk.pool.on('relay:connect', (relay: NDKRelay) => { + console.log(`Relay connected: ${relay.url}`); + }); + ``` + +### 5. Optimize Event Processing Pipeline + +Based on NDK's efficient event handling: + +- **Implement event processing with proper validation**: + ```typescript + // Only process events that pass validation + if (!this.skipValidation) { + if (!ndkEvent.isValid) { + return; + } + } + ``` + +- **Use NDK's event queuing and batch processing**: + ```typescript + // Batch event processing for better performance + const updateEntries = (reason: string) => { + const newSlice = entriesFromIds(newEntriesRef.current); + // ... process in batch rather than individually + } + ``` + +- **Implement EOSE (End of Stored Events) handling** more effectively: + ```typescript + // Handle EOSE with improved timing + subscription.on('eose', () => { + if (isMounted) { + setLoading(false); + setEose(true); + // Process any accumulated events after EOSE + updateEntries('eose'); + } + }); + ``` + +### 6. Implement Progressive Loading + +From NDK's subscription approach: + +- **Use cache-first loading with fallback to relays**: + ```typescript + // Try cache first for immediate feedback + if (ndk.cacheAdapter) { + const cachedEvents = await ndk.cacheAdapter.query(subscription); + if (cachedEvents.length > 0) { + // Process cached events immediately + processCachedEvents(cachedEvents); + } + } + ``` + +- **Implement pagination with proper thresholds**: + ```typescript + // Only fetch more when needed + if (hasMore && !loading && !refreshing) { + loadMore(oldestTimestamp - 1); + } + ``` + +## Key Architectural Improvements + +1. **More Robust Feed State Management**: + - Use `useRef` for maintaining state references that don't trigger re-renders + - Implement batch updates for feed state changes + - Use proper cleanup and event deduplication + +2. **Enhanced Subscription Management**: + - Use `NDKSubscription` with appropriate options + - Implement proper grouping and caching strategies + - Handle subscription lifecycle with proper cleanup + +3. **Contact List Reliability**: + - Use multiple approaches to fetch and validate contacts + - Implement caching and retry logic for reliable contact detection + - Handle edge cases (like self-follows) appropriately + +4. **Event Processing Optimization**: + - Implement more efficient event filtering and processing + - Use batch processing for events after EOSE + - Add proper validation and verification + +5. **Performance Enhancements**: + - Use NDK's optimization patterns like subscription grouping + - Implement proper memory management for large event sets + - Use efficient data structures (Map/Set) for event tracking + +## Implementation Plan + +### Current Code Structure + +The current implementation consists of: + +1. **useFeedEvents.ts** - Core hook for handling feed events +2. **useFeedHooks.ts** - Specialized feed hooks (Following, POWR, Global) +3. **useFeedMonitor.ts** - For monitoring feed state and auto-refresh +4. **powr.tsx** - The POWR tab screen component +5. **following.tsx** - The Following tab screen component +6. **global.tsx** - The Global tab screen component +7. **EnhancedSocialPost.tsx** - Component for rendering feed posts +8. **socialFeedService.ts** - Service for interacting with Nostr + +### Files to Update (Modify): + +1. **useFeedEvents.ts** - Update with NDK's subscription patterns and better event processing + - Improve subscription lifecycle management + - Implement better event deduplication + - Add batch processing and performance optimizations + +2. **useFeedHooks.ts** - Enhance contact list fetching and filter creation + - Implement multiple approaches for contact list fetching + - Improve filter creation for better relevance + - Remove hardcoded fallbacks in favor of more reliable mechanisms + +3. **useFeedMonitor.ts** - Minor updates for integration with new feed structure + - Enhance refresh mechanisms + - Improve state tracking + +4. **following.tsx** - Update to use the new feed architecture + - Remove debug code once implementation is stable + - Improve UI for feed state feedback + +5. **powr.tsx** - Update to fix infinite loop issues + - Improve component lifecycle management + - Enhance performance + +6. **global.tsx** - Update for consistency with other feed implementations + - Ensure consistent behavior across all feed tabs + +7. **socialFeedService.ts** - Enhance with better relay interaction + - Improve subscription creation + - Add better event filtering + +### Files to Create: + +1. **useFeedState.ts** - New hook for centralized feed state management + ```typescript + // Manage feed entries state with efficient updates + export function useFeedState() { + // Implementation here + } + ``` + +2. **useFeedSubscription.ts** - New hook for subscription management + ```typescript + // Handle NDK subscriptions with proper lifecycle + export function useFeedSubscription(filters: NDKFilter[]) { + // Implementation here + } + ``` + +3. **types/feed.ts** - Enhanced type definitions for feed entries + ```typescript + // More comprehensive feed entry types + export interface FeedEntry { + // Enhanced type definition + } + ``` + +4. **utils/feedUtils.ts** - Utility functions for feed operations + ```typescript + // Feed-related utility functions + export function deduplicateEvents() { + // Implementation + } + ``` + +5. **components/feed/FeedList.tsx** - Shared component for feed rendering + ```typescript + // Reusable feed list component with virtualization + export function FeedList({ entries, onItemPress }) { + // Implementation + } + ``` + +### Implementation Order + +1. First, create the new type definitions in **types/feed.ts** +2. Then, implement the new hooks in **useFeedSubscription.ts** and **useFeedState.ts** +3. Update **useFeedEvents.ts** and **useFeedHooks.ts** with improved implementations +4. Create utility functions in **utils/feedUtils.ts** +5. Implement the shared component in **components/feed/FeedList.tsx** +6. Finally, update the screen components to use the new architecture + +This approach allows gradual refactoring of the codebase while maintaining functionality throughout the process. Each step builds on the previous one, ultimately resulting in a more robust and efficient feed implementation. + +## Related Documentation + +- [Social Architecture](./architecture.md) - Overall social features architecture +- [Feed Filtering](./feed_filtering.md) - Rules for filtering content in social feeds +- [Cache Implementation](./cache_implementation.md) - Details on the social feed caching system +- [NDK Comprehensive Guide](../../technical/ndk/comprehensive_guide.md) - Reference for NDK implementation +- [NDK Subscription Analysis](../../technical/ndk/subscription_analysis.md) - Analysis of NDK subscription patterns +- [MVP and Targeted Rebuild](../../project/mvp_and_rebuild.md) - Overall project roadmap and priorities diff --git a/docs/features/workout/completion_flow.md b/docs/features/workout/completion_flow.md new file mode 100644 index 0000000..10de107 --- /dev/null +++ b/docs/features/workout/completion_flow.md @@ -0,0 +1,292 @@ +# Workout Completion Flow + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Workout Module, Nostr Integration, Social Sharing + +## Purpose + +This document defines the requirements, design decisions, and implementation plan for the POWR app's workout completion flow. It outlines the multi-step process for completing workouts with options for local storage, Nostr publishing, template management, and social sharing. This specification serves as the architectural foundation for the `WorkoutCompletionFlow` component and related services. + + +## Problem Statement +Users need a clear, privacy-respecting process for completing workouts, with options to save locally and/or publish to Nostr, update templates based on changes made during the workout, and optionally share their accomplishments socially. The current implementation lacks a structured flow for these decisions and doesn't address privacy concerns around workout metrics. + +## Requirements + +### Functional Requirements +- Allow users to complete workouts and save data locally +- Provide options to publish workouts to Nostr with complete or limited data +- Enable social sharing of workout accomplishments +- Support template updates based on workout modifications +- Maintain proper attribution for templates +- Support offline completion with queued publishing +- Present clear workout summary and celebration screens + +### Non-Functional Requirements +- Privacy: Control over what workout metrics are published +- Performance: Completion flow should respond within 500ms +- Reliability: Work offline with 100% data retention +- Usability: Max 3 steps to complete a workout +- Security: Secure handling of Nostr keys and signing +- Consistency: Match Nostr protocol specifications (NIP-4e) + +## Design Decisions + +### 1. Three-Tier Storage Approach +Implement a tiered approach to workout data storage and sharing: Local Only, Publish to Nostr (Complete/Limited), and Social Sharing. + +**Rationale**: +- Provides users with clear control over their data privacy +- Aligns with the Nostr protocol's decentralized nature +- Balances social engagement with privacy concerns +- Enables participation regardless of privacy preferences + +**Trade-offs**: +- Additional complexity in the UI +- More complex data handling logic +- Potential confusion around data visibility + +### 2. Template Update Handling +When users modify a workout during execution, offer options to: Keep Original Template, Update Existing Template, or Save as New Template. + +**Rationale**: +- Supports natural evolution of workout templates +- Maintains history and attribution +- Prevents accidental template modifications +- Enables template personalization + +**Trade-offs**: +- Additional decision point for users +- Version tracking complexity +- Potential template proliferation + +### 3. Conflict Resolution Strategy +Implement a "Last Write Wins with Notification" approach for template conflicts, with options to keep local changes, accept remote changes, or create a fork. + +**Rationale**: +- Simple to implement and understand +- Provides user awareness of conflicts +- Maintains user control over conflict resolution +- Avoids blocking workout completion flow + +**Trade-offs**: +- May occasionally result in lost updates +- Requires additional UI for conflict resolution +- Can create multiple versions of templates + +## Technical Design + +### Core Components + +```typescript +// Workout Completion Options +interface WorkoutCompletionOptions { + storageType: 'local_only' | 'publish_complete' | 'publish_limited'; + shareOnSocial: boolean; + socialMessage?: string; + templateAction: 'keep_original' | 'update_existing' | 'save_as_new'; + newTemplateName?: string; +} + +// Nostr Event Creation +interface NostrEventCreator { + createWorkoutRecord( + workout: Workout, + options: WorkoutCompletionOptions + ): NostrEvent; + + createSocialShare( + workoutRecord: NostrEvent, + message: string + ): NostrEvent; + + updateTemplate( + originalTemplate: WorkoutTemplate, + modifiedWorkout: Workout + ): NostrEvent; +} + +// Publishing Queue +interface PublishingQueue { + queueEvent(event: NostrEvent): Promise<void>; + processQueue(): Promise<void>; + getQueueStatus(): { pending: number, failed: number }; +} + +// Conflict Resolution +interface ConflictResolver { + detectConflicts(localTemplate: WorkoutTemplate, remoteTemplate: WorkoutTemplate): boolean; + resolveConflict( + localTemplate: WorkoutTemplate, + remoteTemplate: WorkoutTemplate, + resolution: 'use_local' | 'use_remote' | 'create_fork' + ): WorkoutTemplate; +} +``` + +### Workout Completion Flow + +```typescript +async function completeWorkout( + workout: Workout, + options: WorkoutCompletionOptions +): Promise<CompletionResult> { + // 1. Save complete workout data locally + await saveWorkoutLocally(workout); + + // 2. Handle template updates if needed + if (workout.templateId && workout.hasChanges) { + await handleTemplateUpdate(workout, options.templateAction, options.newTemplateName); + } + + // 3. Publish to Nostr if selected + let workoutEvent: NostrEvent | null = null; + if (options.storageType !== 'local_only') { + const isLimited = options.storageType === 'publish_limited'; + workoutEvent = await publishWorkoutToNostr(workout, isLimited); + } + + // 4. Create social share if selected + if (options.shareOnSocial && workoutEvent) { + await createSocialShare(workoutEvent, options.socialMessage || ''); + } + + // 5. Return completion status + return { + success: true, + localId: workout.id, + nostrEventId: workoutEvent?.id, + pendingSync: !navigator.onLine + }; +} +``` + +## Implementation Plan + +### Phase 1: Core Completion Flow +1. Implement workout completion confirmation dialog +2. Create completion options screen with storage choices +3. Build local storage functionality with workout summary +4. Add workout celebration screen with achievements +5. Implement template difference detection + +### Phase 2: Nostr Integration +1. Implement workout record (kind 1301) publishing +2. Add support for limited metrics publishing +3. Create template update/versioning system +4. Implement social sharing via kind 1 posts +5. Add offline queue with sync status indicators + +### Phase 3: Refinement and Enhancement +1. Add conflict detection and resolution +2. Implement template attribution preservation +3. Create version history browsing +4. Add advanced privacy controls +5. Implement achievement recognition system + +## Testing Strategy + +### Unit Tests +- Template difference detection +- Nostr event generation (complete and limited) +- Social post creation +- Conflict detection +- Privacy filtering logic + +### Integration Tests +- End-to-end workout completion flow +- Offline completion and sync +- Template update scenarios +- Cross-device template conflict resolution +- Social sharing with quoted content + +### User Testing +- Template modification scenarios +- Privacy control understanding +- Conflict resolution UX +- Workout completion satisfaction + +## Observability + +### Logging +- Workout completion events +- Publishing attempts and results +- Template update operations +- Conflict detection and resolution +- Offline queue processing + +### Metrics +- Completion rates +- Publishing success rates +- Social sharing frequency +- Template update frequency +- Offline queue size and processing time + +## Future Considerations + +### Potential Enhancements +- Collaborative template editing +- Richer social sharing with images/graphics +- Template popularity and trending metrics +- Coach/trainee permission model +- Interactive workout summary visualizations + +### Known Limitations +- Limited to Nostr protocol constraints +- No guaranteed deletion of published content +- Template conflicts require manual resolution +- No cross-device real-time sync +- Limited to supported NIP implementations + +## Dependencies + +### Runtime Dependencies +- Nostr NDK for event handling +- SQLite for local storage +- Expo SecureStore for key management +- Connectivity detection for offline mode + +### Development Dependencies +- TypeScript for type safety +- React Native testing tools +- Mock Nostr relay for testing +- UI/UX prototyping tools + +## Security Considerations +- Private keys never exposed to application code +- Local workout data encrypted at rest +- Clear indication of what data is being published +- Template attribution verification +- Rate limiting for publishing operations + +## Rollout Strategy + +### Development Phase +1. Implement core completion flow with local storage +2. Add Nostr publishing with complete/limited options +3. Implement template handling and conflict resolution +4. Add social sharing capabilities +5. Implement comprehensive testing suite + +### Production Deployment +1. Release to limited beta testing group +2. Monitor completion flow metrics and error rates +3. Gather feedback on privacy controls and template handling +4. Implement refinements based on user feedback +5. Roll out to all users with clear documentation + +## References +- [NIP-4e: Workout Events](https://github.com/nostr-protocol/nips/blob/4e-draft/4e.md) +- [POWR Social Features Design Document](https://github.com/docNR/powr/blob/main/docs/design/SocialDesignDocument.md) +- [Nostr NDK Documentation](https://github.com/nostr-dev-kit/ndk) +- [Offline-First Application Architecture](https://blog.flutter.io/offline-first-application-architecture-a2c4b2c61c8b) +- [React Native Performance Optimization](https://reactnative.dev/docs/performance) + +## Related Documentation + +- [Nostr Exercise NIP](../../technical/nostr/exercise_nip.md) - Technical specification for Nostr workout events +- [Workout Features Overview](./index.md) - Overview of all workout-related features +- [Social Architecture](../social/architecture.md) - Social integration architecture +- [Publication Queue Service](../../technical/nostr/offline_queue.md) - Offline-first sync queue implementation +- [Project MVP and Targeted Rebuild](../../project/mvp_and_rebuild.md) - Overall project roadmap and priorities diff --git a/docs/features/workout/data_models.md b/docs/features/workout/data_models.md new file mode 100644 index 0000000..2ec293c --- /dev/null +++ b/docs/features/workout/data_models.md @@ -0,0 +1,692 @@ +# Workout Data Models + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Workout Features, Data Layer + +## Purpose + +This document details the data structures, persistence strategies, and transformation logic for the workout feature. It covers the entire data lifecycle from workout creation through completion, storage, and analysis. + +## Data Flow Overview + +```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 +``` + +## Core 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; +} +``` + +### Workout Model + +```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; +} + +type WorkoutType = 'strength' | 'circuit' | 'emom' | 'amrap'; + +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; +} + +type SetType = 'normal' | 'warmup' | 'dropset' | 'failure' | 'amrap'; +``` + +### 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; +} +``` + +### Personal Records + +```typescript +interface PersonalRecord { + id: string; + exerciseId: string; + metric: 'weight' | 'reps' | 'volume'; + value: number; + workoutId: string; + achievedAt: number; +} +``` + +## Data Transformation + +### 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 { + const { template, workoutSettings = {} } = params; + + // Create base workout + const workout: Workout = { + id: generateUuid(), + title: template.title, + type: template.type, + startTime: Date.now(), + isCompleted: false, + templateId: template.id, + exercises: [], + tags: [...template.tags], + lastUpdated: Date.now() + }; + + // Process exercises + const exercisesToInclude = template.exercises + .filter(e => !workoutSettings.skipExercises?.includes(e.id)); + + workout.exercises = exercisesToInclude.map((templateExercise, index) => { + // Transform each exercise + const workoutExercise: WorkoutExercise = { + id: generateUuid(), + sourceId: templateExercise.id, + title: templateExercise.title, + sets: templateExercise.sets.map((templateSet, setIndex) => ({ + id: generateUuid(), + setNumber: setIndex + 1, + type: templateSet.type, + weight: templateSet.weight * (workoutSettings.scaleWeights || 1), + reps: templateSet.reps, + isCompleted: false, + isDirty: false + })), + isCompleted: false, + isDirty: false, + order: index, + restTime: workoutSettings.adjustRestTimes + ? adjustRestTime(templateExercise.restTime) + : templateExercise.restTime + }; + + return workoutExercise; + }); + + // Add any additional exercises + if (workoutSettings.addExercises) { + workout.exercises = [ + ...workout.exercises, + ...workoutSettings.addExercises.map((e, i) => ({ + ...e, + order: workout.exercises.length + i + })) + ]; + } + + return workout; +} +``` + +## Database Schema + +```sql +-- Active 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) +); + +-- Workout tags +CREATE TABLE IF NOT EXISTS workout_tags ( + workout_id TEXT NOT NULL, + tag TEXT NOT NULL, + FOREIGN KEY(workout_id) REFERENCES 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 workouts(id) ON DELETE CASCADE +); +``` + +## Data Persistence Strategies + +### Incremental Saving + +```typescript +class WorkoutPersistence { + // Save entire workout + async saveWorkout(workout: Workout): Promise<void> { + return this.db.withTransactionAsync(async () => { + // 1. Save workout metadata + await this.db.runAsync( + `INSERT OR REPLACE INTO workouts + (id, title, type, start_time, end_time, completed, notes, template_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [ + workout.id, + workout.title, + workout.type, + workout.startTime, + workout.endTime || null, + workout.isCompleted ? 1 : 0, + workout.notes || null, + workout.templateId || null + ] + ); + + // 2. Save exercises + for (const exercise of workout.exercises) { + await this.db.runAsync( + `INSERT OR REPLACE INTO workout_exercises + (id, workout_id, exercise_id, position, notes) + VALUES (?, ?, ?, ?, ?)`, + [ + exercise.id, + workout.id, + exercise.sourceId, + exercise.order, + exercise.notes || null + ] + ); + + // 3. Save sets + for (const set of exercise.sets) { + await this.db.runAsync( + `INSERT OR REPLACE INTO workout_sets + (id, workout_exercise_id, set_number, weight, reps, rpe, completed, set_type, timestamp) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + set.id, + exercise.id, + set.setNumber, + set.weight || null, + set.reps || null, + set.rpe || null, + set.isCompleted ? 1 : 0, + set.type, + set.timestamp || null + ] + ); + } + } + }); + } + + // Save only modified data + async saveIncrementalChanges(workout: Workout): Promise<void> { + const dirtyExercises = workout.exercises.filter(e => e.isDirty); + + return this.db.withTransactionAsync(async () => { + // Update workout metadata + await this.db.runAsync( + `UPDATE workouts SET + title = ?, type = ?, end_time = ?, completed = ?, notes = ? + WHERE id = ?`, + [ + workout.title, + workout.type, + workout.endTime || null, + workout.isCompleted ? 1 : 0, + workout.notes || null, + workout.id + ] + ); + + // Only update changed exercises and sets + for (const exercise of dirtyExercises) { + await this.db.runAsync( + `UPDATE workout_exercises SET + position = ?, notes = ? + WHERE id = ?`, + [ + exercise.order, + exercise.notes || null, + exercise.id + ] + ); + + // Update dirty sets + const dirtySets = exercise.sets.filter(s => s.isDirty); + for (const set of dirtySets) { + await this.db.runAsync( + `UPDATE workout_sets SET + weight = ?, reps = ?, rpe = ?, completed = ?, timestamp = ? + WHERE id = ?`, + [ + set.weight || null, + set.reps || null, + set.rpe || null, + set.isCompleted ? 1 : 0, + set.timestamp || null, + set.id + ] + ); + set.isDirty = false; + } + + exercise.isDirty = false; + } + }); + } +} +``` + +### Auto-save Implementation + +```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; +} +``` + +## Nostr Integration + +### 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), + }; +} +``` + +## Error Handling and Recovery + +```typescript +async function checkForUnfinishedWorkouts(): Promise<Workout | null> { + try { + const activeWorkouts = await db.getAllAsync<ActiveWorkoutRow>( + 'SELECT * FROM workouts WHERE end_time IS NULL AND completed = 0' + ); + + 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; + } +} + +async function reconstructWorkoutFromDatabase(workoutId: string): Promise<Workout | null> { + try { + // Fetch workout + const workoutRow = await db.getFirstAsync<WorkoutRow>( + 'SELECT * FROM workouts WHERE id = ?', + [workoutId] + ); + + if (!workoutRow) return null; + + // Fetch exercises + const exerciseRows = await db.getAllAsync<WorkoutExerciseRow>( + 'SELECT * FROM workout_exercises WHERE workout_id = ? ORDER BY position', + [workoutId] + ); + + // Construct workout object + const workout: Workout = { + id: workoutRow.id, + title: workoutRow.title, + type: workoutRow.type as WorkoutType, + startTime: workoutRow.start_time, + endTime: workoutRow.end_time || undefined, + isCompleted: Boolean(workoutRow.completed), + templateId: workoutRow.template_id || undefined, + notes: workoutRow.notes || undefined, + exercises: [], + tags: [], + lastUpdated: workoutRow.last_updated || Date.now() + }; + + // Fetch tags + const tagRows = await db.getAllAsync<{ tag: string }>( + 'SELECT tag FROM workout_tags WHERE workout_id = ?', + [workoutId] + ); + + workout.tags = tagRows.map(row => row.tag); + + // Populate exercises with sets + for (const exerciseRow of exerciseRows) { + // Fetch sets + const setRows = await db.getAllAsync<WorkoutSetRow>( + 'SELECT * FROM workout_sets WHERE workout_exercise_id = ? ORDER BY set_number', + [exerciseRow.id] + ); + + const exercise: WorkoutExercise = { + id: exerciseRow.id, + sourceId: exerciseRow.exercise_id, + title: exerciseRow.title || '', // Fetch from exercise if needed + sets: setRows.map(setRow => ({ + id: setRow.id, + setNumber: setRow.set_number, + type: setRow.set_type as SetType, + weight: setRow.weight || undefined, + reps: setRow.reps || undefined, + rpe: setRow.rpe || undefined, + isCompleted: Boolean(setRow.completed), + isDirty: false, + timestamp: setRow.timestamp || undefined, + })), + notes: exerciseRow.notes || undefined, + isDirty: false, + isCompleted: Boolean(exerciseRow.completed), + order: exerciseRow.position, + restTime: exerciseRow.rest_time || undefined + }; + + workout.exercises.push(exercise); + } + + return workout; + } catch (error) { + console.error('Error reconstructing workout:', error); + return null; + } +} +``` + +## Analytics and Metrics + +```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; +} +``` + +## Implementation Status + +### Completed + +- Core data model definitions +- SQLite schema implementation +- Basic persistence layer +- Template-to-workout conversion +- Workout state management + +### In Progress + +- Nostr event integration +- PR detection and achievements +- Enhanced recovery mechanisms +- Analytics metrics calculation + +### Planned + +- Synchronization with other devices +- Historical trend analysis +- Machine learning integration for performance insights +- Enhanced backup mechanisms + +## Related Documentation + +- [Workout Overview](./workout_overview.md) - General workout feature architecture +- [UI Components](./ui_components.md) - UI components using these data models +- [Completion Flow](./completion_flow.md) - Workout completion and saving +- [Implementation Roadmap](./implementation_roadmap.md) - Development timeline +- [Nostr Exercise NIP](../../technical/nostr/exercise_nip.md) - Nostr protocol specification diff --git a/docs/features/workout/index.md b/docs/features/workout/index.md new file mode 100644 index 0000000..a4eba4b --- /dev/null +++ b/docs/features/workout/index.md @@ -0,0 +1,44 @@ +# Workout Feature Documentation + +This section contains documentation related to the workout feature of the POWR app, which is a core part of our MVP. + +## Key Documents + +- [Completion Flow](./completion_flow.md) - Documentation for the workout completion process +- [Data Models](./data_models.md) - Workout data model specifications +- [UI Components](./ui_components.md) - Workout UI component specifications + +## Feature Overview + +The workout feature allows users to: + +1. Create new workouts from templates +2. Execute workouts with tracking for sets, reps, and weights +3. Complete workouts and view history +4. Share workouts to Nostr social feeds + +This feature is central to the POWR app experience and is prioritized for the MVP release. + +## Key Components + +- Workout creation flow +- Active workout UI +- Rest timer functionality +- Workout completion and sharing +- Workout history and statistics + +## Implementation Considerations + +- Mobile-friendly design for workout tracking on the go +- Offline support for workouts without connectivity +- Performance optimizations for smooth UI during intense usage +- Nostr integration for workout sharing +- Local storage with SQLite for workout history + +## Related Documentation + +- [Social Integration](../social/architecture.md) - How workouts integrate with social features +- [History](../history/index.md) - Workout history implementation +- [Nostr Exercise NIP](../../technical/nostr/exercise_nip.md) - Nostr protocol specification for exercises + +**Last Updated:** 2025-03-25 diff --git a/docs/features/workout/ui_components.md b/docs/features/workout/ui_components.md new file mode 100644 index 0000000..7b358ab --- /dev/null +++ b/docs/features/workout/ui_components.md @@ -0,0 +1,351 @@ +# Workout UI Components + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Workout Features, UI Implementation + +## Purpose + +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 + +## Implementation Status + +### Completed Components +- WorkoutHeader +- Exercise Navigation +- Basic Set Tracker +- Rest Timer (initial version) + +### In Progress +- Exercise Details Panel +- Enhanced Set Tracker +- Workout Summary screen + +### Planned +- Advanced state transitions +- Specialized workout type layouts +- Full accessibility implementation + +## Related Documentation + +- [Workout Overview](./workout_overview.md) - General workout feature architecture +- [Data Models](./data_models.md) - Data structures used by these components +- [Completion Flow](./completion_flow.md) - Details of the workout completion process +- [Implementation Roadmap](./implementation_roadmap.md) - Timeline for component development diff --git a/docs/features/workout/workout_overview.md b/docs/features/workout/workout_overview.md new file mode 100644 index 0000000..8104ed8 --- /dev/null +++ b/docs/features/workout/workout_overview.md @@ -0,0 +1,321 @@ +# Workout Tab Overview + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Workout Features, Core Functionality + +## Purpose + +This document provides a comprehensive overview of the workout tab functionality in the POWR app, including architecture, implementation details, and development roadmap. It covers the technical design of workout tracking, exercise management, and workout completion flow. + +## 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 Status + +### Completed +- Core workout context and reducer implementation +- Basic exercise tracking UI +- Template-to-workout conversion +- SQLite persistence layer +- Rest timer functionality + +### In Progress +- Workout summary screen enhancements +- PR detection and tracking +- Circuit workout support + +### Planned +- EMOM/AMRAP workout types +- Advanced statistics and tracking +- Full Nostr integration for sharing + +## 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 + +## Related Documentation + +- [Workout Completion Flow](./completion_flow.md) - Details on the workout completion process +- [UI Components](./ui_components.md) - Workout-specific UI components +- [Data Models](./data_models.md) - Workout data structures +- [Implementation Roadmap](./implementation_roadmap.md) - Phased implementation plan +- [Nostr Exercise NIP](../../technical/nostr/exercise_nip.md) - Technical specification for workout event format diff --git a/docs/guides/index.md b/docs/guides/index.md new file mode 100644 index 0000000..87e90a0 --- /dev/null +++ b/docs/guides/index.md @@ -0,0 +1,17 @@ +# Developer Guides + +This section contains guides and best practices for POWR app development. + +## Available Guides + +- [Coding Style Guide](./coding_style.md) - Standards and conventions for code quality +- [AI Collaboration Guide](./ai_collaboration_guide.md) - How to effectively collaborate using AI tools +- [Interface Design Guide](./writing_good_interfaces.md) - Guidelines for designing clear interfaces + +## Coming Soon + +- Documentation Style Guide - Standards for maintaining documentation +- Testing Guide - Best practices for writing tests +- Performance Optimization Guide - Tips for improving app performance + +**Last Updated:** 2025-03-25 diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..c54c07d --- /dev/null +++ b/docs/index.md @@ -0,0 +1,25 @@ +# POWR App Documentation + +Welcome to the POWR App documentation. This documentation is organized by category to help you find the information you need. + +## Documentation Categories + +- [Guides](./guides/index.md) - Developer guides and practices +- [Architecture](./architecture/index.md) - System-wide architectural documentation +- [Features](./features/index.md) - Feature-specific documentation +- [Technical](./technical/index.md) - Technical implementation details +- [Testing](./testing/index.md) - Testing documentation +- [Project](./project/index.md) - Project management documentation + +## Key Documents + +- [MVP and Targeted Rebuild](./project/mvp_and_rebuild.md) - Our plan for the MVP release and targeted rebuilds +- [Long-Term Roadmap](./project/long_term_roadmap.md) - Strategic vision and future feature roadmap +- [NDK Comprehensive Guide](./technical/ndk/comprehensive_guide.md) - Complete guide to NDK implementation +- [Coding Style Guide](./guides/coding_style.md) - Coding standards for the project + +## Contributing to Documentation + +Please follow the guidelines in the [Documentation Guide](./guides/documentation_guide.md) when updating or adding documentation. + +**Last Updated:** 2025-03-26 diff --git a/docs/project/documentation/implementation_script.md b/docs/project/documentation/implementation_script.md new file mode 100644 index 0000000..9cfc3a5 --- /dev/null +++ b/docs/project/documentation/implementation_script.md @@ -0,0 +1,334 @@ +# Documentation Reorganization Implementation Script + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Documentation Standards, Project Organization + +## Purpose + +This document provides a practical, step-by-step approach to implementing the documentation reorganization outlined in the organization plan. It includes specific commands, actions, and validation steps to ensure a smooth migration process. + +## Prerequisites + +- Git command line tools +- Access to repository +- Node.js (for running any scripts we might create) +- Text editor for modifying files + +## Phase 1: Setup and Preparation + +### 1. Create a dedicated branch for documentation updates + +```bash +git checkout -b docs/reorganization +``` + +### 2. Create the new folder structure + +```bash +# Create the main directory structure +mkdir -p docs/guides +mkdir -p docs/architecture +mkdir -p docs/features/workout +mkdir -p docs/features/social +mkdir -p docs/features/profile +mkdir -p docs/features/library +mkdir -p docs/features/powr_packs +mkdir -p docs/features/history +mkdir -p docs/features/settings +mkdir -p docs/technical/ndk +mkdir -p docs/technical/caching +mkdir -p docs/technical/styling +mkdir -p docs/technical/nostr +mkdir -p docs/testing +mkdir -p docs/project +mkdir -p docs/project/documentation +mkdir -p docs/tools +mkdir -p docs/archive +``` + +### 3. Create placeholders for critical new documents + +```bash +touch docs/index.md +touch docs/guides/index.md +touch docs/architecture/index.md +touch docs/features/index.md +touch docs/technical/index.md +touch docs/testing/index.md +touch docs/project/index.md +``` + +## Phase 2: Migration of High-Priority Documents + +### 1. Move the MVP and Targeted Rebuild document + +```bash +cp docs/design/MVPandTargetedRebuild.md docs/project/mvp_and_rebuild.md +``` + +Update the document with a new header that includes: +- Last updated date +- Status: Active +- Add proper cross-references to other documents + +### 2. Move the NDK Comprehensive Guide + +```bash +cp docs/design/Analysis/NDK_Comprehensive_Guide.md docs/technical/ndk/comprehensive_guide.md +``` + +Update the document with: +- Last updated date +- Status: Active +- Cross-references to other NDK-related documents + +### 3. Consolidate Social Architecture Documentation + +Review and merge insights from: +- docs/design/Social/POWRSocialArchitecture.md +- docs/design/Social/POWRSocialFeedImplementationPlan.md +- docs/design/Social/UpdatedPlan.md + +```bash +touch docs/features/social/architecture.md +``` + +Create a comprehensive document that consolidates the key information. + +### 4. Update Database Architecture Documentation + +```bash +cp docs/design/database_architecture.md docs/architecture/database_architecture.md +``` + +Update with latest database schema and patterns. + +### 5. Move Coding Style Guide + +```bash +cp docs/coding_style.md docs/guides/coding_style.md +``` + +## Phase 3: Iterative Content Migration + +For each remaining document, follow this process: + +1. Review using the review process criteria +2. Determine whether to: + - Migrate (with updates) + - Consolidate with other documents + - Archive as outdated +3. Update content and move to new location + +### Migration Script + +Create a simple node script to assist with migrations: + +```javascript +// doc-migrator.js +const fs = require('fs'); +const path = require('path'); + +// Usage: node doc-migrator.js <source> <destination> <title> <status> +const [source, destination, title, status] = process.argv.slice(2); + +if (!source || !destination || !title || !status) { + console.error('Usage: node doc-migrator.js <source> <destination> <title> <status>'); + process.exit(1); +} + +// Read source content +console.log(`Reading source file: ${source}`); +const sourceContent = fs.readFileSync(source, 'utf8'); + +// Create new content with template +const today = new Date().toISOString().slice(0, 10); +const newContent = `# ${title} + +**Last Updated:** ${today} +**Status:** ${status} +**Related To:** [Fill in related component] + +## Purpose + +[Brief description of this document's purpose] + +${sourceContent.replace(/^# .+(\r?\n|\r)/, '')} + +## Related Documentation + +- [Add related document links] +`; + +// Ensure destination directory exists +const destDir = path.dirname(destination); +if (!fs.existsSync(destDir)) { + fs.mkdirSync(destDir, { recursive: true }); +} + +// Write new file +console.log(`Writing to destination: ${destination}`); +fs.writeFileSync(destination, newContent); +console.log(`Successfully migrated ${source} to ${destination}`); + +// Create a simple link checker to help validate links +const checkLinksScript = ` +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const docsRoot = path.resolve(__dirname, '..'); + +// Get all markdown files +const allMdFiles = execSync(\`find \${docsRoot} -name "*.md"\`).toString().split('\\n').filter(Boolean); + +// Track all files and links +const allFiles = new Set(allMdFiles.map(f => path.relative(docsRoot, f))); +const brokenLinks = []; + +allMdFiles.forEach(file => { + const content = fs.readFileSync(file, 'utf8'); + const relativeFile = path.relative(docsRoot, file); + + // Find markdown links + const linkRegex = /\\[.*?\\]\\((.*?)\\)/g; + let match; + + while ((match = linkRegex.exec(content)) !== null) { + const link = match[1]; + + // Skip external links and anchors + if (link.startsWith('http') || link.startsWith('#')) continue; + + // Resolve relative to the file + const fileDir = path.dirname(relativeFile); + const resolvedLink = path.normalize(path.join(fileDir, link)); + + // Check if file exists + if (!allFiles.has(resolvedLink)) { + brokenLinks.push({ file: relativeFile, link, resolvedLink }); + } + } +}); + +if (brokenLinks.length > 0) { + console.log('Found broken links:'); + brokenLinks.forEach(({ file, link, resolvedLink }) => { + console.log(\`In \${file}: Broken link \${link} (resolves to \${resolvedLink})\`); + }); + process.exit(1); +} else { + console.log('All links are valid!'); +} +`; + +fs.writeFileSync('check-links.js', checkLinksScript); +console.log('Created link checker script: check-links.js'); +``` + +Run this script for each document to migrate: + +```bash +node doc-migrator.js docs/design/WorkoutCompletion.md docs/features/workout/completion_flow.md "Workout Completion Flow" "Active" +``` + +## Phase 4: Archive Outdated Documents + +For documents determined to be outdated: + +```bash +# Example +mkdir -p docs/archive/social +cp docs/design/Social/OldImplementationPlan.md docs/archive/social/old_implementation_plan.md +``` + +Add an archive notice at the top: + +```markdown +> **ARCHIVED DOCUMENT**: This document is outdated and kept for historical reference only. Please refer to [Current Document](path/to/current/doc.md) for up-to-date information. +``` + +## Phase 5: Create Index Documents + +For each section, create an index document that lists and describes the contents: + +```markdown +# Technical Documentation + +This section contains technical documentation for various aspects of the POWR app. + +## Contents + +- [NDK Documentation](./ndk/index.md): Nostr Development Kit implementation details +- [Caching Documentation](./caching/index.md): Caching strategies and implementations +- [Styling Documentation](./styling/index.md): Styling approach and patterns +- [Nostr Documentation](./nostr/index.md): Nostr protocol implementation details +``` + +## Phase 6: Update References + +### 1. Search for document references in the codebase + +```bash +grep -r "docs/design" --include="*.ts" --include="*.tsx" . +``` + +### 2. Update each reference to point to the new location + +### 3. Update cross-references between documents + +## Phase 7: Validation and Testing + +### 1. Verify all links work + +Use the link checker script created during the migration process: + +```bash +node docs/tools/check-links.js +``` + +### 2. Manual review of most critical documents + +## Phase 8: Commit Changes + +```bash +git add docs +git commit -m "Reorganize documentation to improve structure and discoverability + +- Implemented new folder structure for better organization +- Migrated key documents to appropriate locations +- Updated content with consistent formatting +- Created index files for better navigation +- Archived outdated documents +- Fixed cross-references between documents" + +git push origin docs/reorganization +``` + +## Phase 9: Create Pull Request + +Create a pull request with a detailed description of changes, including: + +1. Overview of reorganization +2. Summary of major content updates +3. Instructions for developers to find documentation they previously used + +## Success Checklist + +- [ ] All documents follow the new structure +- [ ] No redundant documentation exists +- [ ] All documentation reflects current implementation or plans +- [ ] Key MVP documentation is prominently placed +- [ ] Naming conventions are consistent throughout +- [ ] All cross-references between documents work +- [ ] Obsolete documents are properly archived +- [ ] New documentation fills important gaps +- [ ] Main index.md provides clear navigation + +## Related Documentation + +- [Documentation Organization Plan](./organization_plan.md) - Overall documentation organization strategy +- [Documentation Review Process](./review_process.md) - Process for reviewing documentation quality +- [Documentation Migration Mapping](./migration_mapping.md) - Mapping of source files to destinations +- [Documentation Standards](./standards.md) - Detailed documentation standards diff --git a/docs/project/documentation/migration_mapping.md b/docs/project/documentation/migration_mapping.md new file mode 100644 index 0000000..fb697ab --- /dev/null +++ b/docs/project/documentation/migration_mapping.md @@ -0,0 +1,144 @@ +# Documentation Migration Mapping + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Documentation Organization, Migration Process + +## Purpose + +This document tracks the migration of documentation files from their original locations to their new destinations in the reorganized documentation structure. It serves as both a migration checklist and a reference for finding documents that have been moved. + +## Migration Status Legend + +- ✅ **Migrated**: File has been migrated to new location +- 🔄 **In Progress**: Migration started but not complete +- ⏳ **Pending**: Not yet migrated +- 🔀 **Consolidated**: Combined with other document(s) +- 📁 **Archived**: Moved to archive due to obsolescence +- ❌ **Deprecated**: Content no longer relevant, not migrated + +## High Priority Documents + +| Status | Original Path | New Path | Notes | +|--------|--------------|----------|-------| +| ✅ | docs/design/MVPandTargetedRebuild.md | docs/project/mvp_and_rebuild.md | Updated with proper metadata and cross-references | +| ✅ | docs/design/Analysis/NDK_Comprehensive_Guide.md | docs/technical/ndk/comprehensive_guide.md | Updated with new links | +| ✅ | docs/design/DocumentationOrganizationPlan.md | docs/project/documentation/organization_plan.md | Updated with links to related docs | +| ✅ | docs/design/DocumentationReviewPrompt.md | docs/project/documentation/review_process.md | Updated with new structure references | +| ✅ | docs/design/DocumentationImplementationScript.md | docs/project/documentation/implementation_script.md | Updated script for new structure | +| ✅ | docs/design/nostr-exercise-nip.md | docs/technical/nostr/exercise_nip.md | - | +| ✅ | docs/design/WorkoutCompletion.md | docs/features/workout/completion_flow.md | - | +| ✅ | docs/design/POWR Pack/POWRPack.md | docs/features/powr_packs/overview.md | Enhanced with proper structure and metadata | +| ✅ | docs/design/POWR Pack/POWR_Pack_Implementation_Plan.md | docs/features/powr_packs/implementation_plan.md | Technical content reorganized and expanded | +| ✅ | docs/design/Analysis/NDKSubscriptionAnalysis.md | docs/technical/ndk/subscription_analysis.md | Enhanced with code examples and best practices | +| ✅ | docs/design/Analysis/NDKandNip19.md | docs/technical/nostr/encoding_decoding.md | Complete guide with practical examples | +| ⏳ | docs/coding_style.md | docs/guides/coding_style.md | - | + +## NDK Related Documents + +| Status | Original Path | New Path | Notes | +|--------|--------------|----------|-------| +| ✅ | docs/design/Analysis/NDK_Comprehensive_Guide.md | docs/technical/ndk/comprehensive_guide.md | - | +| ✅ | docs/design/Analysis/NDKSubscriptionAnalysis.md | docs/technical/ndk/subscription_analysis.md | Enhanced with code examples and best practices | +| ✅ | docs/design/Analysis/NDKandNip19.md | docs/technical/nostr/encoding_decoding.md | Complete guide with practical examples | +| ⏳ | docs/design/Analysis/NDKFunctionHexKeys.md | docs/technical/ndk/function_hex_keys.md | - | + +## POWR Pack Related Documents + +| Status | Original Path | New Path | Notes | +|--------|--------------|----------|-------| +| ✅ | docs/design/POWR Pack/POWRPack.md | docs/features/powr_packs/overview.md | Enhanced with proper structure and metadata | +| ✅ | docs/design/POWR Pack/POWR_Pack_Implementation_Plan.md | docs/features/powr_packs/implementation_plan.md | Technical content reorganized and expanded | + +## Social Related Documents + +| Status | Original Path | New Path | Notes | +|--------|--------------|----------|-------| +| ✅ | docs/design/Social/POWRSocialArchitecture.md | docs/features/social/architecture.md | Enhanced with structure diagrams | +| ✅ | docs/design/Social/POWRSocialFeedImplementationPlan.md | docs/features/social/feed_implementation_details.md | Enhanced with code examples | +| ✅ | docs/design/Social/UpdatedPlan.md | docs/features/social/implementation_plan.md | Enhanced with NDK patterns | +| ✅ | docs/design/Social/SocialFeedFilteringRules.md | docs/features/social/feed_filtering.md | Enhanced with implementation details | +| ✅ | docs/design/Social/SocialFeedCacheImplementation.md | docs/features/social/cache_implementation.md | Enhanced with error handling examples | +| 📁 | docs/design/Social/ImplementationPlan.md | - | Superseded by newer documents | +| 📁 | docs/design/Analysis/OlasSocialFeed.md | - | Historical analysis, superseded by newer approaches | + +## Workout Related Documents + +| Status | Original Path | New Path | Notes | +|--------|--------------|----------|-------| +| ✅ | docs/design/WorkoutCompletion.md | docs/features/workout/completion_flow.md | - | +| ✅ | docs/design/WorkoutTab/WorkoutTabDesignDoc.md | docs/features/workout/workout_overview.md | Enhanced with implementation status | +| ✅ | docs/design/WorkoutTab/WorkoutUIComponentSpec.md | docs/features/workout/ui_components.md | Enhanced with accessibility details | +| ✅ | docs/design/WorkoutTab/WorkoutDataFlowSpec.md | docs/features/workout/data_models.md | Enhanced with error handling examples | +| ⏳ | docs/design/WorkoutTab/WorkoutImplementationRoadmap.md | docs/features/workout/implementation_roadmap.md | - | +| ⏳ | docs/design/WorkoutTab/Summary.md | 🔀 | To be consolidated into workout_overview.md | +| ⏳ | docs/design/WorkoutTab/HistoryTabEnhancementDesignDoc.md | docs/features/history/history_overview.md | - | +| ⏳ | docs/design/WorkoutHistory/MigrationGuide.md | docs/features/history/migration_guide.md | - | + +## Profile Related Documents + +| Status | Original Path | New Path | Notes | +|--------|--------------|----------|-------| +| ⏳ | docs/design/ProfileTab/ProfileTabEnhancementDesignDoc.md | docs/features/profile/profile_enhancement.md | - | + +## Library Related Documents + +| Status | Original Path | New Path | Notes | +|--------|--------------|----------|-------| +| ⏳ | docs/design/library_tab.md | docs/features/library/library_overview.md | - | +| ⏳ | docs/design/Templates/TemplateOrganization.md | docs/features/library/template_organization.md | - | +| ⏳ | docs/design/template-creation-design.md | 🔀 | To be consolidated into template_organization.md | + +## Technical Documents + +| Status | Original Path | New Path | Notes | +|--------|--------------|----------|-------| +| ✅ | docs/design/nostr-exercise-nip.md | docs/technical/nostr/exercise_nip.md | - | +| ⏳ | docs/design/database_architecture.md | docs/architecture/database_architecture.md | - | +| ⏳ | docs/design/database_implementation.md | docs/architecture/database_implementation.md | - | +| ⏳ | docs/design/cache-management.md | docs/technical/caching/cache_management.md | - | +| ⏳ | docs/design/styling_design_doc.md | docs/technical/styling/styling_guide.md | - | + +## Settings Related Documents + +| Status | Original Path | New Path | Notes | +|--------|--------------|----------|-------| +| ⏳ | docs/design/Settings/SettingsImplementationGuide.md | docs/features/settings/implementation_guide.md | - | + +## Testing Related Documents + +| Status | Original Path | New Path | Notes | +|--------|--------------|----------|-------| +| ⏳ | docs/testing/ContactCacheServiceTests.md | docs/testing/contact_cache_service_tests.md | - | +| ⏳ | docs/testing/CacheImplementationTesting.md | docs/testing/cache_testing.md | - | + +## Other Documents + +| Status | Original Path | New Path | Notes | +|--------|--------------|----------|-------| +| ⏳ | docs/ai_collaboration_guide.md | docs/guides/ai_collaboration_guide.md | - | +| ⏳ | docs/writing_good_interfaces.md | docs/guides/writing_good_interfaces.md | - | +| ⏳ | docs/design/RNR-original-example.tsx | 📁 | Historical reference only | + +## Migration Progress + +- **High Priority Documents**: 11/12 (92%) +- **NDK Related Documents**: 3/4 (75%) +- **POWR Pack Related Documents**: 2/2 (100%) +- **Social Related Documents**: 5/7 (71%) +- **Workout Related Documents**: 4/8 (50%) +- **Profile Related Documents**: 0/1 (0%) +- **Library Related Documents**: 0/3 (0%) +- **Technical Documents**: 1/5 (20%) +- **Settings Related Documents**: 0/1 (0%) +- **Testing Related Documents**: 0/2 (0%) +- **Other Documents**: 0/3 (0%) + +**Overall Progress**: 26/48 (54%) + +## Related Documentation + +- [Documentation Organization Plan](./organization_plan.md) - Overall documentation organization strategy +- [Documentation Review Process](./review_process.md) - Process for reviewing documentation quality +- [Documentation Implementation Script](./implementation_script.md) - Implementation details for migration +- [Documentation Standards](./standards.md) - Detailed documentation standards diff --git a/docs/project/documentation/organization_plan.md b/docs/project/documentation/organization_plan.md new file mode 100644 index 0000000..2079077 --- /dev/null +++ b/docs/project/documentation/organization_plan.md @@ -0,0 +1,203 @@ +# POWR App Documentation Organization Plan + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Documentation Standards, Project Organization + +## Purpose + +This document outlines the plan for reorganizing the POWR app documentation to improve consistency, accessibility, and maintainability. + +## Current Documentation Issues + +After examining our documentation structure, we've identified several issues: + +1. **Inconsistent organization**: Some documents are in feature-specific folders while others are directly in the `/design/` directory +2. **Scattered analysis documents**: Technical analysis documents are spread across different locations +3. **Redundant documentation**: Multiple files covering the same features with overlapping information +4. **Poor visibility of key documents**: Critical documents like MVPandTargetedRebuild.md aren't prominently placed +5. **Inconsistent naming conventions**: Mix of camelCase, PascalCase, and snake_case in folder and file names +6. **Outdated documentation**: Some documents no longer reflect current implementation or plans + +## Proposed Documentation Structure + +``` +docs/ +├── guides/ # Developer guides and practices +│ ├── coding_style.md # Coding standards and practices +│ ├── ai_collaboration_guide.md # How to work with AI tools +│ ├── documentation_guide.md # How to write and maintain docs +│ └── writing_good_interfaces.md # Interface design principles +│ +├── architecture/ # System-wide architectural documentation +│ ├── system_overview.md # High-level system architecture +│ ├── data_flow.md # App-wide data flow patterns +│ ├── database_architecture.md # Database design and schema +│ ├── state_management.md # State management approach +│ └── offline_support.md # Offline functionality architecture +│ +├── features/ # Feature-specific documentation +│ ├── workout/ # Workout feature documentation +│ │ ├── workout_overview.md # Feature overview +│ │ ├── ui_components.md # UI component specifications +│ │ ├── data_models.md # Data models and interfaces +│ │ ├── completion_flow.md # Workout completion flow +│ │ └── implementation_roadmap.md # Implementation plan +│ │ +│ ├── social/ # Social feature documentation +│ │ ├── social_overview.md # Feature overview +│ │ ├── architecture.md # Social feature architecture +│ │ ├── feed_implementation.md # Feed implementation details +│ │ ├── caching_strategy.md # Caching approach +│ │ └── filtering_rules.md # Content filtering rules +│ │ +│ ├── profile/ # Profile feature documentation +│ │ └── profile_enhancement.md # Profile tab enhancements +│ │ +│ ├── library/ # Library feature documentation +│ │ ├── library_overview.md # Feature overview +│ │ └── template_organization.md # Template organization +│ │ +│ ├── powr_packs/ # POWR Packs feature documentation +│ │ ├── overview.md # Feature overview +│ │ └── implementation_plan.md # Implementation plan +│ │ +│ ├── history/ # History feature documentation +│ │ ├── history_overview.md # Feature overview +│ │ └── migration_guide.md # Migration guide +│ │ +│ └── settings/ # Settings feature documentation +│ └── implementation_guide.md # Implementation guide +│ +├── technical/ # Technical documentation +│ ├── ndk/ # NDK-related documentation +│ │ ├── comprehensive_guide.md # Comprehensive NDK guide +│ │ ├── subscription_analysis.md # Subscription handling analysis +│ │ └── encoding_decoding.md # NIP-19 encoding/decoding +│ │ +│ ├── caching/ # Caching documentation +│ │ └── cache_implementation.md # Cache implementation details +│ │ +│ ├── styling/ # Styling documentation +│ │ └── styling_guide.md # Styling approach and patterns +│ │ +│ └── nostr/ # Nostr protocol documentation +│ └── exercise_nip.md # Nostr exercise NIP +│ +├── testing/ # Testing documentation +│ ├── testing_strategy.md # Overall testing approach +│ ├── cache_testing.md # Cache implementation testing +│ └── contact_service_tests.md # Contact service testing +│ +├── project/ # Project management documentation +│ ├── mvp_and_rebuild.md # MVP and targeted rebuild plan +│ ├── roadmap.md # Project roadmap +│ ├── release_process.md # Release process documentation +│ └── documentation/ # Documentation about documentation +│ ├── organization_plan.md # This document +│ ├── review_process.md # Documentation review process +│ ├── implementation_script.md # Implementation scripts +│ ├── migration_mapping.md # Source → destination mapping +│ └── standards.md # Documentation standards +│ +├── tools/ # Documentation tools +│ ├── doc-migrator.js # Migration script +│ ├── check-links.js # Link validation script +│ └── README.md # Tool documentation +│ +└── archive/ # Archived/obsolete documentation + └── [outdated documents] # Outdated documentation files +``` + +## Implementation Strategy + +1. **Create New Structure**: Create the new folder structure while preserving existing files +2. **Document Review**: Review all existing documentation using the review_process.md criteria +3. **Migrate Content**: + - Move files to their appropriate locations in the new structure + - Rename files to follow consistent conventions + - Update cross-references between documents +4. **Consolidate Documentation**: + - Merge redundant documentation + - Create new overview documents for each major feature area +5. **Archive Outdated Content**: + - Move obsolete documentation to the archive folder + - Add notices to archived docs indicating they're outdated +6. **Update References**: + - Search codebase for references to documentation + - Update any links in code comments or README files +7. **Documentation Index**: + - Create an index file for each major section + - Add a main index.md at the root of the docs directory + +## File Naming Conventions + +- Use `snake_case` for all documentation filenames for consistency +- Use descriptive but concise names that clearly indicate content +- For feature-specific docs, prefix with feature name: `social_architecture.md` +- For technical docs, use descriptive names: `subscription_analysis.md` + +## Content Guidelines + +- Each document should begin with a clear title and purpose statement +- Include a last-updated date in each document +- Include a table of contents for longer documents +- Use consistent heading levels (# for title, ## for major sections, etc.) +- Include code examples where appropriate +- Link to related documentation when referencing other concepts +- Include diagrams for complex systems or flows + +## Priority Documents + +The following documents should be prioritized in the migration process: + +1. **docs/project/mvp_and_rebuild.md** (moved from MVPandTargetedRebuild.md) +2. **docs/technical/ndk/comprehensive_guide.md** (moved from NDK_Comprehensive_Guide.md) +3. **docs/features/social/architecture.md** (consolidated from social docs) +4. **docs/architecture/database_architecture.md** (updated from existing) +5. **docs/guides/coding_style.md** (moved from root) + +## Document Example: Migration Template + +For each document that needs to be migrated, use this template: + +```markdown +# [Document Title] + +**Last Updated:** [Date] +**Status:** [Active/Archived/Draft] +**Related To:** [Feature/Component] + +## Purpose + +Brief description of this document's purpose. + +## Content + +Main document content goes here. + +## Related Documentation + +- [Link to related doc 1] +- [Link to related doc 2] +``` + +## Success Criteria + +The documentation reorganization will be considered successful when: + +1. All documentation follows the new structure +2. No redundant documentation exists in multiple locations +3. All documentation is updated to reflect current implementation or plans +4. Key MVP-related documentation is prominently accessible +5. Naming conventions are consistent throughout +6. All cross-references between documents are functional +7. Obsolete documents are properly archived +8. New documentation is created to fill important gaps + +## Related Documentation + +- [Documentation Review Process](./review_process.md) - How to review documentation for quality and accuracy +- [Documentation Migration Implementation](./implementation_script.md) - Specific implementation details +- [Documentation Migration Mapping](./migration_mapping.md) - Mapping of source files to destinations +- [Documentation Standards](./standards.md) - Detailed documentation standards diff --git a/docs/project/documentation/review_process.md b/docs/project/documentation/review_process.md new file mode 100644 index 0000000..9b6da68 --- /dev/null +++ b/docs/project/documentation/review_process.md @@ -0,0 +1,134 @@ +# Documentation Review Process + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Documentation Standards, Quality Assurance + +## Purpose + +This document guides a thorough review of the POWR app documentation against the current codebase, with special focus on ensuring alignment with our MVP plan. This review process ensures our documentation accurately reflects implementation details, architectural decisions, and development priorities. + +## Review Process + +### 1. Documentation Inventory Assessment +First, review all existing documentation to establish a baseline: + +- Identify all documentation files across the repository +- Categorize by type (architecture, feature specs, API definitions, implementation guides) +- Note document creation/modification dates +- List key documentation gaps (undocumented components, features, or interfaces) + +### 2. MVP Alignment Review +With [MVP and Targeted Rebuild](../mvp_and_rebuild.md) as your primary reference: + +- Verify all MVP features are clearly documented with implementation plans +- Confirm that targeted rebuilds are thoroughly specified with rationale and approach +- Assess whether each component marked for rebuild has adequate technical documentation +- Identify any contradictions between the MVP plan and other documentation +- Validate phasing and prioritization details match across all documents + +### 3. Code-Documentation Accuracy Assessment +For each key component identified in the MVP plan: + +- Compare component documentation against actual implementation +- Review interface definitions for accuracy +- Verify architectural diagrams match actual code organization +- Check that documented dependencies reflect actual imports +- Confirm data flow descriptions match the implemented patterns +- Validate hook usage patterns match documentation + +### 4. Social Feature Documentation Deep-Dive +Since social features need significant attention in our MVP: + +- Analyze NDK implementation against [NDK Comprehensive Guide](../../technical/ndk/comprehensive_guide.md) +- Verify relay management documentation matches implementation +- Validate subscription handling patterns against best practices +- Confirm authentication flows match documentation +- Check caching strategy implementation against documentation +- Assess error handling and offline support implementations + +### 5. Component-Level Documentation Check +For each major component: + +- Compare props/interfaces in code vs. documentation +- Verify state management patterns match documentation +- Confirm component lifecycle handling follows documented patterns +- Assess whether component relationships are accurately documented +- Check that component rendering optimizations are documented + +### 6. Hook and Service Documentation Validation +For all custom hooks and services: + +- Verify parameter and return type documentation +- Confirm usage examples are accurate and up-to-date +- Check that documented side effects match implementation +- Validate error handling patterns match documentation +- Ensure dependencies are correctly documented + +### 7. Documentation Update Plan +Based on the review, create a prioritized update plan: + +- List documentation that requires immediate updates (high priority) +- Identify documentation needing moderate updates (medium priority) +- Note documentation requiring minor corrections (low priority) +- Suggest new documentation needed to support the MVP implementation +- Recommend documentation that can be deprecated or archived + +## Review Questions + +For each document, answer these key questions: + +1. **Accuracy**: Does this document accurately reflect the current codebase implementation? +2. **Completeness**: Does it cover all necessary aspects of the feature/component? +3. **Consistency**: Is it consistent with the MVP and Targeted Rebuild plan and other documents? +4. **Clarity**: Is the information presented clearly for developers to understand? +5. **Actionability**: Does it provide clear guidance for implementation work? +6. **Relevance**: Is this documentation still relevant to our MVP goals? +7. **Technical Detail**: Does it provide sufficient technical details for implementation? +8. **Examples**: Are code examples current and functional? +9. **Edge Cases**: Does it address important edge cases and error scenarios? + +## Output Format + +For each reviewed document, provide: + +``` +## [Document Name](path/to/document) + +### Summary +Brief overview of the document's purpose and current state + +### Accuracy Assessment +Detailed findings on documentation vs. code accuracy + +### Alignment with MVP Plan +How well this aligns with MVP and Targeted Rebuild plan + +### Key Issues +List of specific inaccuracies, gaps or outdated information + +### Update Recommendations +Prioritized list of suggested updates + +### Required New Sections +Any new content needed to support the MVP implementation + +### Code References +Links to relevant code that validates or contradicts documentation +``` + +## Final Deliverables + +1. Comprehensive documentation review report +2. Prioritized documentation update plan +3. List of new documentation needed +4. Specific recommendations for MVP and Targeted Rebuild plan enhancements +5. Updated architectural diagrams (if needed) +6. Component relationship maps for key MVP features + +## Related Documentation + +- [Documentation Organization Plan](./organization_plan.md) - Overall documentation organization strategy +- [Documentation Migration Implementation](./implementation_script.md) - Implementation details for documentation migration +- [MVP and Targeted Rebuild](../mvp_and_rebuild.md) - Project roadmap and MVP plan +- [Documentation Standards](./standards.md) - Detailed documentation standards diff --git a/docs/project/documentation/standards.md b/docs/project/documentation/standards.md new file mode 100644 index 0000000..2752c4a --- /dev/null +++ b/docs/project/documentation/standards.md @@ -0,0 +1,191 @@ +# Documentation Standards + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Documentation Organization, Content Guidelines + +## Purpose + +This document defines the standards for documentation in the POWR app repository. It provides guidelines for formatting, structure, content quality, and maintenance to ensure consistency and clarity across all documentation. + +## Document Structure + +### File Organization + +- All documentation should be organized according to the structure defined in [organization_plan.md](./organization_plan.md) +- Use `snake_case` for all filenames (e.g., `state_management.md` not `StateManagement.md`) +- Group related documents in appropriate directories +- Use meaningful directory and file names that clearly indicate content + +### Document Header + +Every document should begin with a consistent header: + +```markdown +# Document Title + +**Last Updated:** YYYY-MM-DD +**Status:** [Active|Draft|Archived] +**Related To:** [Feature/Component] + +## Purpose + +Brief description of this document's purpose and what it covers. +``` + +### Status Types + +- **Active**: Current documentation that accurately reflects the codebase +- **Draft**: Work in progress, not yet complete or verified +- **Archived**: Kept for historical reference but no longer reflects current state +- **Superseded**: Replaced by newer document (include link to replacement) + +## Content Guidelines + +### General Principles + +1. **Clear and Concise**: Use simple, direct language +2. **Accurate**: Must reflect current implementation +3. **Complete**: Cover all necessary aspects of the topic +4. **Structured**: Use logical organization with clear sections +5. **Audience-Aware**: Consider who will read the document (developers, designers, etc.) + +### Writing Style + +- Use present tense ("The function returns..." not "The function will return...") +- Use active voice when possible ("The system stores data..." not "Data is stored by the system...") +- Be direct and concise +- Define acronyms and technical terms on first use +- Use consistent terminology throughout + +### Headings and Sections + +- Use clear, descriptive headings +- Follow heading hierarchy (# → ## → ### → ####) +- Don't skip levels (e.g., don't go from ## to ####) +- Keep heading structure logical and consistent + +### Code Examples + +- Include relevant code examples where helpful +- Use syntax highlighting with appropriate language tags + +```typescript +// Good example with syntax highlighting +function calculateTotal(items: Item[]): number { + return items.reduce((sum, item) => sum + item.price, 0); +} +``` + +- Ensure examples are accurate and follow coding standards +- Include comments in code examples when needed for clarity +- Keep examples concise and focused on the topic + +### Lists and Tables + +- Use bulleted lists for unordered items +- Use numbered lists for sequential steps or prioritized items +- Use tables for comparing multiple items across consistent criteria +- Include headers for tables + +### Diagrams and Visual Aids + +- Include diagrams for complex systems or workflows +- Use consistent styles and colors in diagrams +- Provide text descriptions of diagrams for accessibility +- Reference diagrams in the text +- Use standard formats: Mermaid for code-based diagrams, PNG/SVG for images + +Example of a Mermaid diagram: + +```mermaid +graph TD + A[Start] --> B{Is authenticated?} + B -->|Yes| C[Show Dashboard] + B -->|No| D[Show Login] + D --> E[Authenticate] + E --> C +``` + +## Cross-Referencing + +### Internal References + +- Use relative links to reference other documentation +- Include descriptive link text, not just URLs or "here" +- Use section anchors for deep linking when appropriate + +Examples: +```markdown +See [Authentication Architecture](../architecture/authentication.md) for details. + +For more information about the login flow, see the [Authentication Process](../architecture/authentication.md#authentication-process). +``` + +### External References + +- Provide full URLs for external references +- Include access dates for content that might change +- Consider archiving critical external content that might disappear + +## Maintenance + +### Update Process + +- Update the "Last Updated" date whenever changing a document +- Review documentation whenever related code changes +- Follow the [Documentation Review Process](./review_process.md) for regular reviews +- Archive outdated documents rather than deleting them + +### Versioning + +- Maintain documentation in sync with code versions +- Clearly indicate which version(s) of code a document applies to +- Create version-specific documentation when necessary for major releases + +## Documentation Types + +Different types of documentation have specific requirements: + +### Architecture Documentation + +- Include high-level architectural diagrams +- Describe system components and their interactions +- Explain key design decisions and trade-offs +- Reference relevant design patterns + +### API Documentation + +- Describe all public APIs/interfaces +- Include parameter types, return values, and exceptions +- Provide usage examples +- Document error handling + +### Feature Documentation + +- Explain the purpose and use cases +- Describe user interactions and workflows +- Include implementation details important for maintenance +- Provide examples of feature usage + +### Tutorial Documentation + +- Use step-by-step instructions +- Include clear, annotated examples +- Explain each step thoroughly +- Build from simple to complex + +## Accessibility + +- Use descriptive link text +- Provide text descriptions for images and diagrams +- Use semantic markup correctly +- Ensure color is not the only way information is conveyed +- Maintain good contrast in diagrams and images + +## Related Documentation + +- [Documentation Organization Plan](./organization_plan.md) - Overall documentation organization strategy +- [Documentation Review Process](./review_process.md) - Process for reviewing documentation quality +- [Documentation Implementation Script](./implementation_script.md) - Implementation details for migration +- [Documentation Migration Mapping](./migration_mapping.md) - Mapping of source files to destinations diff --git a/docs/project/index.md b/docs/project/index.md new file mode 100644 index 0000000..952d1d1 --- /dev/null +++ b/docs/project/index.md @@ -0,0 +1,44 @@ +# Project Documentation + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Project Overview, Documentation Organization + +## Purpose + +This section contains project-level documentation including roadmaps, MVP plans, process documentation, and other project management artifacts. + +## Contents + +- [MVP and Targeted Rebuild](./mvp_and_rebuild.md) - Project roadmap and MVP prioritization +- [Long-Term Roadmap](./long_term_roadmap.md) - Extended vision and strategic direction +- [Documentation Process](#documentation-process) - Documentation organization and process +- [Release Process](./release_process.md) - Release management workflow + +## Documentation Process + +The POWR app has a standardized documentation process to ensure high-quality, consistent documentation: + +- [Documentation Organization Plan](./documentation/organization_plan.md) - The overall strategy for organizing documentation +- [Documentation Review Process](./documentation/review_process.md) - How to review documentation for quality and accuracy +- [Documentation Implementation Script](./documentation/implementation_script.md) - Step-by-step guide for implementing the documentation reorganization +- [Documentation Migration Mapping](./documentation/migration_mapping.md) - Tracking the migration of documentation files +- [Documentation Standards](./documentation/standards.md) - Standards for documentation content and formatting + +## Project Roadmap + +The POWR app development follows a multi-horizon planning approach: + +### Near-Term: [MVP and Targeted Rebuild](./mvp_and_rebuild.md) +1. Core functionality for the Minimum Viable Product +2. Areas targeted for rebuilding or enhancement +3. Prioritization of features and components +4. Development phases and milestones + +### Extended Vision: [Long-Term Roadmap](./long_term_roadmap.md) +1. Strategic pillars and product vision +2. Feature roadmap beyond MVP +3. Technical architecture evolution +4. Growth strategy and metrics + +Both roadmaps are continuously updated as the project progresses, with input from user feedback and stakeholder requirements. diff --git a/docs/project/long_term_roadmap.md b/docs/project/long_term_roadmap.md new file mode 100644 index 0000000..2957a0b --- /dev/null +++ b/docs/project/long_term_roadmap.md @@ -0,0 +1,425 @@ +# POWR App Long-Term Roadmap + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Product Strategy, Social Integration, Architecture + +## Purpose + +This document outlines the long-term vision and roadmap for the POWR app, extending beyond the initial MVP release and social feature rebuild. It provides a comprehensive view of planned features, architectural improvements, and strategic directions for the project. + +## Vision + +POWR aims to become the premier workout tracking and social fitness platform, combining powerful local-first workout functionality with rich social features powered by the Nostr protocol. The platform will enable users to: + +1. Track workouts with detailed metrics and history +2. Share achievements and progress with the fitness community +3. Discover and follow other fitness enthusiasts +4. Access and share workout templates and exercise libraries +5. Build meaningful connections around fitness goals + +## Strategic Pillars + +### 1. Best-in-Class Workout Experience +- Comprehensive workout tracking with detailed metrics +- Intelligent template suggestions and adaptations +- Enhanced analytics and progress visualization +- Seamless offline-first operation + +### 2. Community-Powered Social Layer +- Robust social integration via Nostr +- Rich workout sharing capabilities +- Content discovery and curation +- Community engagement features + +### 3. Privacy-Focused Design +- User control over data sharing +- Decentralized identity through Nostr +- Fine-grained privacy settings +- Transparent data practices + +### 4. Technical Excellence +- Clean architecture with clear separation of concerns +- Reliable performance in all network conditions +- Scalable and maintainable codebase +- Comprehensive testing and monitoring + +## Roadmap Timeline + +### Q2 2025: Foundation + +#### MVP Release +- Complete workout tracking and templates +- Basic Nostr integration +- Simplified social experience +- Core functionality stabilization +- iOS TestFlight release + +#### Architecture Rebuild +- Authentication state management overhaul +- Subscription system rebuild +- Data layer enhancement +- Component decoupling + +### Q3 2025: Social Enhancement + +#### Enhanced Social Features +- Complete social feed implementation +- Profile enhancement +- Interaction capabilities (likes, comments) +- Content discovery improvements + +#### Content Ecosystem +- Enhanced POWR pack sharing +- Template marketplace +- Exercise library expansion +- User content curation + +### Q4 2025: Community and Growth + +#### Community Features +- Fitness challenges and competitions +- User groups and communities +- Achievement system +- Mentorship and coaching features + +#### Platform Expansion +- Android platform release +- Web companion interface +- Multi-device synchronization +- Amber integration for Android + +### 2026 and Beyond: Extended Vision + +#### Advanced Fitness Features +- AI-powered workout recommendations +- Integrated fitness assessment tools +- Nutrition tracking integration +- Professional trainer integration + +#### Enhanced Nostr Capabilities +- Advanced relay management +- Lightning integration for paid content +- Enhanced privacy features +- Cross-app integrations + +## Feature Roadmap + +### Core Workout Experience + +#### Phase 1: Tracking Enhancement +- Advanced metric tracking +- Custom metric creation +- Template variations +- Improved progress visualization + +#### Phase 2: Intelligent Assistance +- Workout recommendations +- Load management suggestions +- Rest and recovery tracking +- Plateau detection + +#### Phase 3: Comprehensive Fitness +- Cardio integration +- Mobility and flexibility tracking +- Integrated periodization +- Multi-sport support + +### Social Features + +#### Phase 1: Basic Social (MVP+) +- POWR official feed +- Basic workout sharing +- Simple profile display +- Following capability + +#### Phase 2: Rich Social Experience +- Enhanced content rendering +- Interactive elements (likes, comments) +- Content discovery +- Enhanced profiles + +#### Phase 3: Community Building +- User groups and communities +- Challenges and competitions +- Content curation and recommendations +- Enhanced engagement features + +### Platform Enhancement + +#### Phase 1: iOS Excellence +- Performance optimization +- UI/UX refinement +- iOS-specific features +- TestFlight feedback integration + +#### Phase 2: Cross-Platform Expansion +- Android adaptation +- Platform-specific optimizations +- Feature parity across platforms +- Shared codebase management + +#### Phase 3: Ecosystem Development +- Web companion interface +- Wearable device integration +- API for third-party integration +- Developer tools and documentation + +## Technical Architecture Evolution + +### Current Architecture +- React Native with Expo +- SQLite for local storage +- NDK for Nostr integration +- Basic caching and offline support + +### Mid-term Architecture (Post-Rebuild) +- Enhanced state management +- Robust subscription system +- Improved caching strategy +- Clean separation of data and UI layers + +### Long-term Architecture Vision +- Comprehensive service layer +- Advanced caching and synchronization +- Extensible plugin architecture +- Enhanced offline capabilities + +### Performance Goals +- App startup: <1.5 seconds +- Feed loading: <300ms +- Offline functionality: 100% core features +- Animation smoothness: 60fps +- Battery impact: <5% per hour of active use + +## Feature Deep Dives + +### Enhanced Social Feed System + +The rebuilt social feed system will provide: + +1. **Feed Types** + - POWR official feed with curated content + - Following feed with content from followed users + - Discovery feed with trending and suggested content + - Custom feeds with user-defined filters + +2. **Content Types** + - Workout records with detailed metrics + - Template shares with preview capabilities + - Standard posts with rich media support + - Articles and guides with structured content + +3. **Interaction Capabilities** + - Likes and reactions + - Comments and discussions + - Reposts and sharing + - Content saving and collections + +4. **Feed Intelligence** + - Content relevance scoring + - User interest modeling + - Trending detection + - Content categorization + +### Advanced Template System + +1. **Template Creation** + - Enhanced template builder + - Version control for templates + - Template variations + - Custom parameters and variables + +2. **Template Discovery** + - Category-based browsing + - Search with advanced filters + - Popularity and trending indicators + - Personalized recommendations + +3. **Template Usage** + - Intelligent adaptation to user progress + - On-the-fly customization + - Performance tracking across users + - Result comparison and benchmarking + +4. **Template Sharing** + - Enhanced template cards in feed + - Preview capabilities + - User reviews and ratings + - Usage statistics + +### Comprehensive Analytics + +1. **Individual Analytics** + - Detailed performance metrics + - Progress visualization + - Trend analysis + - Goal tracking + +2. **Comparative Analytics** + - Benchmarking against similar users + - Historical performance comparison + - Template effectiveness analysis + - Community percentile ranking + +3. **Insight Generation** + - Plateau detection + - Overtraining risk assessment + - Progress acceleration opportunities + - Recovery optimization + +4. **Data Export and Integration** + - Standardized data export + - Integration with health platforms + - Researcher and coach access + - API for third-party analysis + +## Adoption and Growth Strategy + +### User Acquisition + +1. **Initial Focus**: Fitness enthusiasts with technical interests + - Nostr community targeting + - Developer and early adopter outreach + - Technical fitness communities + +2. **Expansion**: Mainstream fitness users + - Enhanced onboarding for non-technical users + - Simplified Nostr integration + - Focus on core workout features + - Peer referral mechanisms + +3. **Broad Market**: General fitness population + - Emphasis on user experience + - Community success stories + - Influencer partnerships + - Content marketing + +### Feature Prioritization Framework + +Features will be prioritized based on: + +1. **User Value**: Impact on core user experience +2. **Technical Foundation**: Dependency on architectural components +3. **Community Request**: Frequency and intensity of user requests +4. **Strategic Alignment**: Contribution to long-term vision +5. **Implementation Complexity**: Resource requirements and risk + +### Feedback Integration + +1. **Continuous User Research** + - In-app feedback mechanisms + - User interviews and testing + - Usage analytics + - Community forum engagement + +2. **Feedback Processing** + - Categorization and prioritization + - Pattern identification + - Root cause analysis + - Solution ideation + +3. **Development Integration** + - Feature request tracking + - User-developer communication + - Testing and validation + - Impact assessment + +## Development Approach + +### Team Structure + +As the project grows, team structure will evolve: + +1. **Initial Phase**: Full-stack developers with cross-functional skills +2. **Growth Phase**: Specialized roles for frontend, backend, and data +3. **Mature Phase**: Feature teams with end-to-end ownership + +### Development Methodology + +1. **Agile Approach** + - Two-week sprints + - Continuous integration and deployment + - Feature flagging for safe releases + - User-centric development + +2. **Technical Practices** + - Test-driven development + - Code review and pair programming + - Documentation as code + - Performance monitoring + +3. **Release Cadence** + - Major releases: Quarterly + - Feature releases: Monthly + - Bug fixes: As needed (within 48 hours for critical issues) + +## Success Metrics + +### User Engagement +- Daily active users (DAU) +- Workout completion rate +- Feature usage distribution +- Retention curves (1, 7, 30, 90 days) + +### Social Activity +- Content creation rate +- Interaction rate (likes, comments) +- Following/follower growth +- Network expansion metrics + +### Technical Performance +- Crash-free sessions +- API response times +- Client-side performance +- Offline capability reliability + +### Business Metrics +- User growth rate +- Cost per acquisition +- Platform expansion metrics +- Developer ecosystem growth + +## Risk Assessment and Mitigation + +### Technical Risks + +1. **Nostr Protocol Evolution** + - **Risk**: Rapid protocol changes could require frequent adaptations + - **Mitigation**: Abstraction layer between Nostr and app logic + +2. **Cross-Platform Consistency** + - **Risk**: Feature disparity between platforms + - **Mitigation**: Platform-agnostic core with platform-specific adaptations + +3. **Performance at Scale** + - **Risk**: Degraded performance with large datasets + - **Mitigation**: Virtual rendering, pagination, and efficient data structures + +### Product Risks + +1. **Feature Complexity** + - **Risk**: Overwhelming users with too many features + - **Mitigation**: Progressive disclosure, guided onboarding + +2. **Social Adoption** + - **Risk**: Low engagement with social features + - **Mitigation**: Value-focused features, seamless integration with core + +3. **Market Competition** + - **Risk**: Similar features from established competitors + - **Mitigation**: Focus on unique differentiators (Nostr, privacy, community) + +## Conclusion + +This long-term roadmap provides a strategic vision for POWR's evolution beyond the initial MVP and rebuild phases. By focusing on core workout functionality, enhancing social features, and expanding to new platforms, POWR aims to create a comprehensive fitness platform that combines powerful tracking capabilities with a vibrant social community. + +The roadmap is designed to be flexible, allowing for adaptation based on user feedback, market conditions, and technological advancements. Regular reviews and updates will ensure that development efforts remain aligned with user needs and strategic goals. + +## Related Documentation + +- [MVP and Targeted Rebuild](./mvp_and_rebuild.md) - Short-term roadmap and rebuild strategy +- [Social Architecture](../features/social/architecture.md) - Architecture for social integration using Nostr +- [Feed Implementation](../features/social/feed_implementation_details.md) - Technical details of feed implementation +- [Implementation Plan](../features/social/implementation_plan.md) - Technical implementation approach +- [Authentication](../architecture/authentication.md) - Authentication flow and state management diff --git a/docs/project/mvp_and_rebuild.md b/docs/project/mvp_and_rebuild.md new file mode 100644 index 0000000..cee3b94 --- /dev/null +++ b/docs/project/mvp_and_rebuild.md @@ -0,0 +1,220 @@ +# MVP and Targeted Social Rebuild + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Product Strategy, Social Integration, Architecture + +## Purpose + +This document outlines the roadmap for the POWR app, defining the Minimum Viable Product (MVP) scope and the subsequent targeted rebuild of social features. It prioritizes core functionality while presenting a phased approach to addressing technical challenges in the current implementation. + +# POWR App Roadmap - MVP and Social Rebuild + +## MVP Definition + +The Minimum Viable Product (MVP) will focus on core functionality while simplifying social features: + +### Core Features (MVP Priority) +- Complete workout tracking and history +- Exercise library and template management +- POWR Pack support +- Basic Nostr integration: + - Ability to publish kind 1301 workout records + - Ability to share workouts with kind 1 notes (quoting 1301 records) + - NIP-89 compliance for app identification + +### Simplified Social Implementation +- Social tab with "Coming Soon" placeholder or minimal POWR official feed +- Profile tab with limited social activity display +- Workout sharing from completion flow (with simplified UI) +- Add workout sharing from history tab + +## Current Technical Challenges + +### Authentication Issues +- Inconsistent auth state management causing cascading problems +- Logout process triggering uncoordinated state changes + +### Subscription Management Problems +- Subscription lifecycle not properly managed +- Resources not being cleaned up consistently + +### React Hook Implementation +- "Rendered fewer hooks than expected" errors +- Component lifecycle hook management issues + +### Component Coupling +- Tight interdependencies between components +- Difficulty isolating fixes for individual components + +## Implementation Phases + +### Phase 1: MVP Stabilization (Current Focus) +- Implement fundamental architecture improvements: + - Authentication state management with clear lifecycle hooks + - Basic subscription management improvements +- Simplify or disable problematic social features +- Add workout sharing from history tab +- Ensure stable workout tracking, history, and template management +- Fix critical bugs in core functionality + +### Phase 2: Social Foundation Rebuild (Post-MVP) +- Complete targeted rebuild of authentication and subscription management +- Implement proper data layer with caching +- Create clear separation between data and UI layers +- Develop and test in parallel with MVP branch + +### Phase 3: Social Feature Re-implementation +- Gradually re-enable social features using new architecture +- Start with simplest screens (e.g., official POWR feed) +- Progress to more complex screens (Following, Global) +- Implement enhanced profile activity view + +### Phase 4: Extended Features +- Amber integration for Android users +- Enhanced social features beyond original implementation +- Additional Nostr integrations and social capabilities + +## Architecture Design + +### 1. Authentication State Management +- Implementation of a proper state machine pattern +- Clear transitions: unauthenticated → authenticating → authenticated → deauthenticating +- Use of Zustand store (aligned with current workoutStore approach) +- Event listeners/callbacks for components to respond to auth changes + +### 2. Subscription Management +- Centralized service for managing subscriptions +- Automatic tracking and cleanup of subscriptions +- Integration with component lifecycle +- Rate limiting and cooldown mechanisms + +### 3. Data Layer Design +- Clean separation between data fetching and UI components +- Proper caching with expiration policies +- Offline support strategy +- Clear interfaces for data services + +### 4. UI Component Structure +- Consistent component patterns across social features +- Proper error boundaries and loading states +- Better separation of concerns between components +- Rebuilt social feed components with cleaner architecture + +## Git and Release Strategy + +### Branch Strategy +- Create `mvp` branch from current state +- Implement MVP simplifications and critical fixes in this branch +- In parallel, start architecture rebuild in `social-rebuild` branch +- Once MVP is released, gradually merge rebuilt components from `social-rebuild` to `main` + +### Feature Flag Implementation +- Add configuration system for feature toggling +- Create conditional rendering for social features +- Define clear interfaces between components to allow swapping implementations +- Store feature flag state in persistent storage for consistency across app launches + +### Release Plan +1. iOS TestFlight (MVP) +2. Implement Amber integration and final Android preparations +3. Android Google Play / APK release +4. Gradual social feature re-enablement through app updates + +## Key Files to Modify + +### MVP Initial Changes +- `app/(tabs)/social/_layout.tsx` - Add "Coming Soon" placeholder or simplified view +- `components/workout/WorkoutCompletionFlow.tsx` - Ensure sharing functionality is stable +- `lib/db/services/NostrWorkoutService.ts` - Review for stability and proper NIP-89 implementation +- `app/(tabs)/history/workoutHistory.tsx` - Add sharing capability + +### Core Architecture Improvements +- `lib/stores/ndk.ts` - Enhance with better auth management +- `lib/hooks/useNDK.ts` - Refactor for more predictable state management +- `components/RelayInitializer.tsx` - Review for subscription management issues +- `lib/hooks/useSubscribe.ts` - Improve subscription lifecycle management + +### Future Rebuild Targets (Post-MVP) +- `lib/hooks/useSocialFeed.ts` - Replace with new service +- `lib/social/socialFeedService.ts` - Refactor with cleaner architecture +- `app/(tabs)/social/*` - Rebuild social feed screens with new architecture +- `components/social/*` - Rebuild social components with consistent patterns + +## Development Timeline + +### 1. Architecture Design: 2-3 days +- Create detailed service interfaces +- Design state management approach +- Document component lifecycle integration + +### 2. Core Service Implementation: 3-5 days +- Build authentication manager +- Implement subscription manager +- Create data fetching services + +### 3. UI Component Rebuild: 5-7 days +- Rebuild one screen at a time +- Implement with new architectural patterns +- Add comprehensive error handling + +### 4. Testing and Integration: 2-3 days +- Test with various network conditions +- Verify authentication edge cases +- Confirm subscription cleanup + +### 5. Cleanup and Documentation: 1-2 days +- Remove deprecated code +- Document new architecture +- Create developer onboarding guide + +## Risk Mitigation +- Implement feature flags to toggle between old and new implementations +- Add enhanced logging during transition +- Create robust error boundaries to prevent cascade failures +- Maintain backward compatibility for core APIs during migration + +## Original Requirements and Questions + +### Simplified MVP Social Experience +- Minimal or no social feed +- Replace social tab with "Coming Soon" placeholder +- Focus on core functionality: + - Allow users to post kind 1 notes quoting 1301 workout records + - Publishing workflow: + 1. User performs workout + 2. User completes workout and opts to share publicly + 3. User edits pre-populated kind 1 note and submits + 4. App publishes kind 1301 workout record, then publishes kind 1 note quoting the record + 5. Result: kind 1 note published to socials, kind 1301 record visible in workout history + - Implement NIP-89 for app identification in published records + +### Key Questions Addressed + +#### Impact on Workout History Functionality +The targeted rebuild approach preserves workout history functionality by focusing primarily on problematic social components. Core authentication and subscription management improvements will benefit the entire app without disrupting workflow. + +#### MVP Architecture Requirements +For a stable MVP with limited social features, we recommend implementing the fundamental Authentication state management and Subscription Management components. These are foundational and will improve stability across all features that use Nostr integration. + +#### Caching Services +Existing caching for user metadata can likely be preserved with clearer interfaces. For the MVP, we can simplify how these caches are used rather than fully rebuilding them. + +#### Workout History Sharing +Adding the ability to share workouts from the history tab would be valuable and consistent with the completion flow sharing functionality. This will require a review of local vs. Nostr event strategies. + +#### Amber Integration +Amber integration should be prioritized after the initial iOS TestFlight release but before wider Android distribution. + +#### Git Strategy +Creating an `mvp` branch from the current state makes sense for the MVP implementation. The feature flag approach will allow gradual introduction of rebuilt components without disrupting the user experience. + + +## Related Documentation + +- [NDK Comprehensive Guide](../technical/ndk/comprehensive_guide.md) - Reference for NDK implementation +- [Social Architecture](../features/social/architecture.md) - Architecture for social integration using Nostr +- [Nostr Exercise NIP](../technical/nostr/exercise_nip.md) - Technical specification for workout event format +- [Subscription Analysis](../technical/ndk/subscription_analysis.md) - Analysis of NDK subscription patterns +- [Offline Queue](../technical/nostr/offline_queue.md) - Implementation details for resilient Nostr publishing +- [Authentication](../architecture/authentication.md) - Authentication flow and state management diff --git a/docs/technical/index.md b/docs/technical/index.md new file mode 100644 index 0000000..ed97d57 --- /dev/null +++ b/docs/technical/index.md @@ -0,0 +1,23 @@ +# Technical Documentation + +This section contains detailed technical documentation about specific technologies and implementations used in the POWR app. + +## Technical Areas + +- [NDK](./ndk/index.md) - Nostr Development Kit implementation details +- [Caching](./caching/index.md) - Caching strategies and implementations +- [Styling](./styling/index.md) - Styling approach and patterns +- [Nostr](./nostr/index.md) - Nostr protocol implementation details + +## Key Technical Documents + +- [NDK Comprehensive Guide](./ndk/comprehensive_guide.md) - Complete guide to NDK implementation +- [Cache Implementation](./caching/cache_implementation.md) - Detailed caching architecture +- [Nostr Exercise NIP](./nostr/exercise_nip.md) - Nostr specification for exercise data + +## Related Documentation + +- [Architecture Documentation](../architecture/index.md) - System-wide architectural documentation +- [Feature Documentation](../features/index.md) - Feature-specific implementations + +**Last Updated:** 2025-03-25 diff --git a/docs/technical/ndk/comprehensive_guide.md b/docs/technical/ndk/comprehensive_guide.md new file mode 100644 index 0000000..c390491 --- /dev/null +++ b/docs/technical/ndk/comprehensive_guide.md @@ -0,0 +1,560 @@ +# NDK Comprehensive Guide + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Nostr Integration, Social Features, State Management + +## Purpose + +This document provides a comprehensive guide to using the Nostr Development Kit (NDK) in the POWR app. It serves as the primary reference for implementing Nostr features, covering core concepts, state management patterns, subscription lifecycles, and mobile-specific considerations. This guide is specifically tailored for the MVP implementation, focusing on simplified social features while establishing a solid foundation for future enhancements. + + +**UPDATED FOR MVP: This guide has been consolidated and aligned with our simplified social approach for the MVP release.** + +This guide combines key information from our various NDK analysis documents and the ndk-mobile package to provide a comprehensive reference for implementing Nostr features in POWR app. It's organized to support our MVP strategy with a focus on the core NDK capabilities we need. + +## Table of Contents + +1. [NDK Core Concepts](#ndk-core-concepts) +2. [Proper State Management](#proper-state-management) +3. [Subscription Lifecycle](#subscription-lifecycle) +4. [NIP-19 and Encoding/Decoding](#nip-19-and-encodingdecoding) +5. [Context Provider Pattern](#context-provider-pattern) +6. [Mobile-Specific Considerations](#mobile-specific-considerations) +7. [Best Practices for POWR MVP](#best-practices-for-powr-mvp) + +## NDK Core Concepts + +NDK (Nostr Development Kit) provides a set of tools for working with the Nostr protocol. Key components include: + +- **NDK Instance**: Central object for interacting with the Nostr network +- **NDKEvent**: Represents a Nostr event +- **NDKUser**: Represents a user (pubkey) +- **NDKFilter**: Defines criteria for querying events +- **NDKSubscription**: Manages subscriptions to event streams +- **NDKRelay**: Represents a connection to a relay + +### Basic Setup + +```typescript +import NDK from '@nostr-dev-kit/ndk'; + +// Initialize NDK with relays +const ndk = new NDK({ + explicitRelayUrls: [ + 'wss://relay.damus.io', + 'wss://relay.nostr.band' + ] +}); + +// Connect to relays +await ndk.connect(); +``` + +## Proper State Management + +The ndk-mobile library emphasizes proper state management using Zustand. This approach is critical for avoiding the issues we've been seeing in our application. + +### Zustand Store Pattern + +```typescript +// Create a singleton store using Zustand +import { create } from 'zustand'; + +// Private store instance - not exported +let store: ReturnType<typeof createNDKStore> | undefined; + +// Store creator function +const createNDKStore = () => create<NDKState>((set) => ({ + ndk: undefined, + initialized: false, + connecting: false, + connectionError: null, + + initialize: (config) => set((state) => { + if (state.initialized) return state; + + const ndk = new NDK(config); + return { ndk, initialized: true }; + }), + + connect: async () => { + set({ connecting: true, connectionError: null }); + try { + const ndk = store?.getState().ndk; + if (!ndk) throw new Error("NDK not initialized"); + + await ndk.connect(); + set({ connecting: false }); + } catch (error) { + set({ connecting: false, connectionError: error }); + } + } +})); + +// Getter for the singleton store +export const getNDKStore = () => { + if (!store) { + store = createNDKStore(); + } + return store; +}; + +// Hook for components to use +export const useNDKStore = <T>(selector: (state: NDKState) => T) => { + // Ensure the store exists + getNDKStore(); + // Return the selected state + return useStore(store)(selector); +}; +``` + +### Critical State Management Points + +1. **Single Store Instance**: Always use a singleton pattern for NDK stores +2. **Lazy Initialization**: Only create the store when first accessed +3. **Proper Selectors**: Select only what you need from the store +4. **Clear State Transitions**: Define explicit state transitions (connecting, connected, error) + +## Subscription Lifecycle + +Proper subscription management is crucial for app stability and performance. The subscribe.ts file in ndk-mobile provides advanced subscription handling. + +### Basic Subscription Pattern + +```typescript +import { useEffect } from 'react'; +import { useNDK } from './hooks/useNDK'; + +function EventComponent({ filter }) { + const { ndk } = useNDK(); + const [events, setEvents] = useState([]); + + useEffect(() => { + if (!ndk) return; + + // Create subscription + const sub = ndk.subscribe(filter, { + closeOnEose: false, // Keep connection open + }); + + // Handle incoming events + sub.on('event', (event) => { + setEvents(prev => [...prev, event]); + }); + + // Start subscription + sub.start(); + + // Critical: Clean up subscription when component unmounts + return () => { + sub.stop(); + }; + }, [ndk, JSON.stringify(filter)]); + + return (/* render events */); +} +``` + +### Enhanced Subscription Hook + +The ndk-mobile package includes an enhanced useSubscribe hook with additional features: + +```typescript +// Example based on ndk-mobile implementation +function useEnhancedSubscribe(filter, options = {}) { + const { ndk } = useNDK(); + const [events, setEvents] = useState([]); + const [eose, setEose] = useState(false); + const subRef = useRef(null); + + useEffect(() => { + if (!ndk) return; + + // Create subscription + const sub = ndk.subscribe(filter, { + closeOnEose: options.closeOnEose || false, + wrap: options.wrap || false + }); + + subRef.current = sub; + + // Handle incoming events + sub.on('event', (event) => { + // Process events (filtering, wrapping, etc.) + + // Add to state + setEvents(prev => { + // Check for duplicates using event.id + if (prev.some(e => e.id === event.id)) return prev; + return [...prev, event]; + }); + }); + + // Handle end of stored events + sub.on('eose', () => { + setEose(true); + }); + + // Start subscription + sub.start(); + + // Clean up + return () => { + if (subRef.current) { + subRef.current.stop(); + } + }; + }, [ndk, JSON.stringify(filter), options]); + + return { events, eose }; +} +``` + +## NIP-19 and Encoding/Decoding + +NIP-19 functions are essential for handling Nostr identifiers like npub, note, and naddr. + +### Decoding NIP-19 Entities + +```typescript +import { nip19 } from '@nostr-dev-kit/ndk'; + +// Decode any NIP-19 entity (naddr, npub, nsec, note, etc.) +function decodeNIP19(encoded: string) { + try { + const decoded = nip19.decode(encoded); + // decoded.type will be 'npub', 'note', 'naddr', etc. + // decoded.data will contain the data specific to that type + return decoded; + } catch (error) { + console.error('Invalid NIP-19 format:', error); + return null; + } +} + +// Convert npub to hex pubkey +function npubToHex(npub: string) { + try { + const decoded = nip19.decode(npub); + if (decoded.type === 'npub') { + return decoded.data as string; // This is the hex pubkey + } + return null; + } catch (error) { + console.error('Invalid npub format:', error); + return null; + } +} +``` + +### Encoding to NIP-19 Formats + +```typescript +// Create an npub from a hex public key +function hexToNpub(hexPubkey: string) { + return nip19.npubEncode(hexPubkey); +} + +// Create a note (event reference) from event ID +function eventIdToNote(eventId: string) { + return nip19.noteEncode(eventId); +} + +// Create an naddr for addressable events +function createNaddr(pubkey: string, kind: number, identifier: string) { + return nip19.naddrEncode({ + pubkey, // Hex pubkey + kind, // Event kind (number) + identifier // The 'd' tag value + }); +} +``` + +## Context Provider Pattern + +The ndk-mobile package emphasizes the Context Provider pattern for proper NDK integration: + +```typescript +import { NDKProvider } from '@nostr-dev-kit/ndk-mobile'; +import App from './App'; + +// Root component +export default function Root() { + return ( + <NDKProvider + params={{ + explicitRelayUrls: [ + 'wss://relay.damus.io', + 'wss://relay.nostr.band' + ] + }} + > + <App /> + </NDKProvider> + ); +} +``` + +This pattern ensures: + +1. **Single NDK Instance**: The entire app shares one NDK instance +2. **Consistent State**: Auth state and relay connections are managed in one place +3. **Hooks Availability**: All NDK hooks (useNDK, useSubscribe, etc.) work correctly +4. **Proper Cleanup**: Connections and subscriptions are managed appropriately + +## Mobile-Specific Considerations + +The ndk-mobile package includes several mobile-specific optimizations: + +### Caching Strategy + +Mobile devices need efficient caching to reduce network usage and improve performance: + +```typescript +import { SQLiteAdapter } from '@nostr-dev-kit/ndk-mobile/cache-adapter/sqlite'; + +// Initialize NDK with SQLite caching +const ndk = new NDK({ + explicitRelayUrls: [...], + cacheAdapter: new SQLiteAdapter({ + dbName: 'nostr-cache.db' + }) +}); +``` + +### Mobile Signers + +For secure key management on mobile devices: + +```typescript +import { SecureStorageSigner } from '@nostr-dev-kit/ndk-mobile/signers/securestorage'; + +// Use secure storage for private keys +const signer = new SecureStorageSigner({ + storageKey: 'nostr-private-key' +}); + +// Add to NDK +ndk.signer = signer; +``` + +## Best Practices for POWR MVP + +Based on our analysis and the ndk-mobile implementation, here are key best practices for our MVP: + +### 1. State Management + +- **Use singleton stores** for NDK and session state +- **Implement proper state machine** for auth transitions with Zustand +- **Add event listeners/callbacks** for components to respond to auth changes + +```typescript +// Authentication state using Zustand (aligned with ndk-mobile patterns) +export const useAuthStore = create((set) => ({ + state: 'unauthenticated', // 'unauthenticated', 'authenticating', 'authenticated', 'deauthenticating' + user: null, + error: null, + + startAuthentication: async (npub) => { + set({ state: 'authenticating', error: null }); + try { + // Authentication logic here + const user = await authenticateUser(npub); + set({ state: 'authenticated', user }); + } catch (error) { + set({ state: 'unauthenticated', error }); + } + }, + + logout: async () => { + set({ state: 'deauthenticating' }); + // Cleanup logic + set({ state: 'unauthenticated', user: null }); + }, +})); +``` + +### 2. Subscription Management + +- **Always clean up subscriptions** when components unmount +- **Keep subscription references** in useRef to ensure proper cleanup +- **Use appropriate cache strategies** to reduce relay load +- **Implement proper error handling** for subscription failures + +```typescript +// Example component with proper subscription management +function WorkoutShareComponent({ workoutId }) { + const { ndk } = useNDK(); + const [relatedPosts, setRelatedPosts] = useState([]); + const subRef = useRef(null); + + useEffect(() => { + if (!ndk) return; + + // Create subscription for posts mentioning this workout + const sub = ndk.subscribe({ + kinds: [1], + '#e': [workoutId] + }, { + closeOnEose: true, + cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST + }); + + subRef.current = sub; + + // Handle events + sub.on('event', (event) => { + setRelatedPosts(prev => [...prev, event]); + }); + + // Start subscription + sub.start(); + + // Clean up on unmount + return () => { + if (subRef.current) { + subRef.current.stop(); + } + }; + }, [ndk, workoutId]); + + // Component JSX +} +``` + +### 3. Simplified Social Features + +For the MVP, we're focusing on: + +- **Workout sharing**: Publishing kind 1301 workout records and kind 1 notes quoting them +- **Official feed**: Display only POWR official account posts in the social tab +- **Static content**: Prefer static content over complex real-time feeds + +```typescript +// Example function to share a workout +async function shareWorkout(ndk, workout, caption) { + // First, publish the workout record (kind 1301) + const workoutEvent = new NDKEvent(ndk); + workoutEvent.kind = 1301; + workoutEvent.content = JSON.stringify(workout); + // Add appropriate tags + workoutEvent.tags = [ + ['d', workout.id], + ['title', workout.title], + ['duration', workout.duration.toString()] + ]; + + // Publish workout record + const workoutPub = await workoutEvent.publish(); + + // Then create a kind 1 note quoting the workout + const noteEvent = new NDKEvent(ndk); + noteEvent.kind = 1; + noteEvent.content = caption || `Just completed ${workout.title}!`; + // Add e tag to reference the workout event + noteEvent.tags = [ + ['e', workoutEvent.id, '', 'quote'] + ]; + + // Publish social note + await noteEvent.publish(); + + return { workoutEvent, noteEvent }; +} +``` + +### 4. Error Handling & Offline Support + +- **Implement graceful fallbacks** for network errors +- **Store pending publish operations** for later retry +- **Use SQLite caching** for offline access to previously loaded data + +```typescript +// Publication queue service example (aligned with mobile patterns) +class PublicationQueue { + async queueForPublication(event) { + try { + // Try immediate publication + await event.publish(); + return true; + } catch (error) { + // Store for later retry + await this.storeEventForLater(event); + return false; + } + } + + async publishPendingEvents() { + const pendingEvents = await this.getPendingEvents(); + for (const event of pendingEvents) { + try { + await event.publish(); + await this.markAsPublished(event); + } catch (error) { + // Keep in queue for next retry + console.error("Failed to publish:", error); + } + } + } +} +``` + +### 5. Provider Pattern + +- **Use NDKProvider** to wrap the application +- **Configure NDK once** at the app root level +- **Access NDK via hooks** rather than creating instances + +```typescript +// App.tsx +export default function App() { + return ( + <NDKProvider + params={{ + explicitRelayUrls: [ + 'wss://relay.damus.io', + 'wss://relay.nostr.band' + ] + }} + > + <YourAppComponents /> + </NDKProvider> + ); +} + +// Using NDK in components +function SomeComponent() { + const { ndk } = useNDK(); + + // Use ndk here + + return (/* component JSX */); +} +``` + +### 6. MVP Implementation Focus + +For the MVP, focus on implementing: + +1. **Core Authentication**: Proper login/logout with state management +2. **Workout Sharing**: Publication of workouts to Nostr +3. **Limited Social Features**: Static feed of official POWR account +4. **User Profile**: Basic user information display + +Defer these for post-MVP: +1. Full social feed implementation +2. Real-time following/interaction +3. Complex subscription patterns + +By following these best practices, we can create a stable foundation for the POWR app's MVP release that addresses the current architectural issues while providing a simplified but functional social experience. + + +## Related Documentation + +- [Nostr Exercise NIP](../nostr/exercise_nip.md) - Technical specification for Nostr workout events +- [Social Architecture](../../features/social/architecture.md) - Social integration architecture +- [Publication Queue Service](../nostr/offline_queue.md) - Offline-first sync queue implementation +- [Project MVP and Targeted Rebuild](../../project/mvp_and_rebuild.md) - Overall project roadmap and priorities +- [NDK Initialization](./initialization.md) - Current implementation of NDK initialization +- [NDK Subscription Analysis](./subscription_analysis.md) - In-depth analysis of NDK subscription patterns diff --git a/docs/technical/ndk/index.md b/docs/technical/ndk/index.md new file mode 100644 index 0000000..9df70bb --- /dev/null +++ b/docs/technical/ndk/index.md @@ -0,0 +1,36 @@ +# NDK (Nostr Development Kit) Documentation + +This section contains technical documentation specific to the Nostr Development Kit (NDK) implementation in the POWR app. + +## Key Documents + +- [Comprehensive Guide](./comprehensive_guide.md) - Complete reference for NDK implementation +- [Subscription Analysis](./subscription_analysis.md) - Analysis of subscription patterns and solutions +- [Encoding and Decoding](./encoding_decoding.md) - NIP-19 encoding and decoding processes + +## Key NDK Concepts + +NDK is the primary library we use for Nostr protocol integration. It provides: + +- Connection and relay management +- Event creation, signing, and publishing +- Subscription management for receiving events +- User management and profile handling +- NIP-19 encoding and decoding utilities + +## Implementation in POWR + +Our implementation of NDK follows these key principles: + +1. Singleton state management via Zustand store +2. Proper subscription lifecycle management +3. Centralized relay configuration +4. Clear authentication flow +5. Simplified implementation for MVP needs + +## Related Documentation + +- [Social Implementation](../../features/social/architecture.md) - How NDK is used in social features +- [Authentication Design](../../architecture/state_management.md) - Authentication architecture + +**Last Updated:** 2025-03-25 diff --git a/docs/technical/ndk/initialization.md b/docs/technical/ndk/initialization.md new file mode 100644 index 0000000..be57679 --- /dev/null +++ b/docs/technical/ndk/initialization.md @@ -0,0 +1,498 @@ +# NDK Initialization + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Nostr Integration, App Startup + +## Purpose + +This document outlines the initialization process for the Nostr Development Kit (NDK) in the POWR app. It explains how NDK is configured, how connections to relays are established, and how the singleton instance is managed throughout the app lifecycle. Proper initialization is critical for consistent behavior, connection management, and efficient resource usage. + +## Overview + +NDK initialization follows a singleton pattern to ensure only one instance exists throughout the application. This prevents resource waste, connection duplication, and inconsistent behavior. The initialization process includes configuring relays, setting up authentication, and establishing connections at the appropriate moment in the app lifecycle. + +## Implementation Details + +### Singleton Pattern + +The NDK instance is managed through a Zustand store that ensures a single source of truth: + +```typescript +// lib/initNDK.ts + +import { create } from 'zustand'; +import NDK from '@nostr-dev-kit/ndk'; +import { NDKRelaySet } from '@nostr-dev-kit/ndk/dist/relay/relay-set'; +import { AppState, AppStateStatus } from 'react-native'; +import { POWR_EXPLICIT_RELAYS } from '@/constants/relays'; + +// Define NDK state type +interface NDKState { + ndk: NDK | null; + initialized: boolean; + connecting: boolean; + connected: boolean; + connectionError: Error | null; + + // Actions + initialize: () => Promise<NDK>; + connect: () => Promise<void>; + disconnect: () => Promise<void>; +} + +// Create a private store instance +const useNDKStore = create<NDKState>((set, get) => ({ + ndk: null, + initialized: false, + connecting: false, + connected: false, + connectionError: null, + + // Initialize NDK instance + initialize: async () => { + // Skip if already initialized + if (get().initialized) return get().ndk!; + + try { + console.log('Initializing NDK'); + + // Create new NDK instance + const ndk = new NDK({ + explicitRelayUrls: POWR_EXPLICIT_RELAYS, + // Other NDK options + enableOutboxModel: true, + cacheAdapter: null, // To be replaced with proper caching + autoConnectUserRelays: false, + liveQueryReconnectTime: 5000 + }); + + // Update state + set({ + ndk, + initialized: true, + connectionError: null + }); + + return ndk; + } catch (error) { + console.error('NDK initialization error:', error); + set({ connectionError: error as Error }); + throw error; + } + }, + + // Connect to relays + connect: async () => { + // Skip if already connecting or connected + if (get().connecting || get().connected) return; + + // Ensure initialization + const ndk = get().ndk || await get().initialize(); + + try { + // Start connecting + set({ connecting: true, connectionError: null }); + + // Connect to relays + await ndk.connect(); + + // Update state + set({ connecting: false, connected: true }); + console.log('NDK connected to relays'); + } catch (error) { + console.error('NDK connection error:', error); + set({ + connecting: false, + connected: false, + connectionError: error as Error + }); + throw error; + } + }, + + // Disconnect from relays + disconnect: async () => { + const { ndk, connected } = get(); + + if (!ndk || !connected) return; + + try { + // Close connections + await ndk.pool.close(); + set({ connected: false }); + console.log('NDK disconnected from relays'); + } catch (error) { + console.error('NDK disconnect error:', error); + } + } +})); + +// App state change handler for connection management +let appStateSubscription: { remove: () => void } | null = null; + +export function initNDK(): Promise<NDK> { + // Set up app state subscription if not already + if (!appStateSubscription) { + appStateSubscription = AppState.addEventListener('change', handleAppStateChange); + } + + return useNDKStore.getState().initialize(); +} + +// Handle app state changes to manage connections +async function handleAppStateChange(nextAppState: AppStateStatus) { + const store = useNDKStore.getState(); + + if (nextAppState === 'active') { + // App came to foreground - connect if not connected + if (store.initialized && !store.connected && !store.connecting) { + await store.connect(); + } + } else if (nextAppState === 'background') { + // App went to background - consider disconnecting to save resources + // This is optional as you may want to maintain connections + // await store.disconnect(); + } +} + +// Export the hook for components +export function useNDK() { + // Custom selector to avoid unnecessary re-renders + const ndk = useNDKStore(state => state.ndk); + const initialized = useNDKStore(state => state.initialized); + const connecting = useNDKStore(state => state.connecting); + const connected = useNDKStore(state => state.connected); + const connectionError = useNDKStore(state => state.connectionError); + + const connect = useNDKStore(state => state.connect); + const disconnect = useNDKStore(state => state.disconnect); + + return { + ndk, + initialized, + connecting, + connected, + connectionError, + connect, + disconnect + }; +} + +// Cleanup function to be called on app unmount +export function cleanupNDK() { + if (appStateSubscription) { + appStateSubscription.remove(); + appStateSubscription = null; + } + + return useNDKStore.getState().disconnect(); +} + +// Export a direct way to get the NDK instance for services +export async function getNDK(): Promise<NDK> { + const { ndk, initialized, initialize } = useNDKStore.getState(); + + if (initialized && ndk) { + return ndk; + } + + return initialize(); +} +``` + +## Usage in Application + +### Application Initialization + +The NDK is initialized at app startup in the main App component: + +```typescript +// app/_layout.tsx + +import { useEffect } from 'react'; +import { initNDK, cleanupNDK } from '@/lib/initNDK'; + +export default function RootLayout() { + // Initialize NDK on app startup + useEffect(() => { + const initializeNDK = async () => { + try { + const ndk = await initNDK(); + await ndk.connect(); + } catch (error) { + console.error('Failed to initialize NDK:', error); + } + }; + + initializeNDK(); + + // Cleanup on unmount + return () => { + cleanupNDK(); + }; + }, []); + + return ( + <Stack> + {/* App content */} + </Stack> + ); +} +``` + +### Component Usage + +Components use the `useNDK` hook to access the NDK instance: + +```typescript +// components/SomeComponent.tsx + +import { useEffect, useState } from 'react'; +import { useNDK } from '@/lib/initNDK'; + +export function SomeComponent() { + const { ndk, initialized, connected, connectionError } = useNDK(); + const [events, setEvents] = useState([]); + + useEffect(() => { + if (!ndk || !connected) return; + + // Use NDK for queries or subscriptions + const subscription = ndk.subscribe( + { kinds: [1], limit: 10 }, + { closeOnEose: false } + ); + + subscription.on('event', (event) => { + setEvents(prev => [...prev, event]); + }); + + subscription.start(); + + return () => { + subscription.stop(); + }; + }, [ndk, connected]); + + if (connectionError) { + return <Text>Error connecting to relays: {connectionError.message}</Text>; + } + + if (!initialized || !connected) { + return <Text>Connecting to Nostr network...</Text>; + } + + return ( + <View> + {/* Component rendering using events */} + </View> + ); +} +``` + +### Service Usage + +Services can use the direct `getNDK` function to access the NDK instance: + +```typescript +// lib/services/SomeService.ts + +import { getNDK } from '@/lib/initNDK'; +import { NDKEvent } from '@nostr-dev-kit/ndk'; + +export class SomeService { + async publishEvent(content: string): Promise<string> { + try { + // Get NDK instance + const ndk = await getNDK(); + + // Create and publish event + const event = new NDKEvent(ndk); + event.kind = 1; + event.content = content; + + // Add tags if needed + event.tags = [ + ['t', 'powr'], + ['t', 'workout'] + ]; + + // Publish to relays + await event.publish(); + + return event.id; + } catch (error) { + console.error('Failed to publish event:', error); + throw error; + } + } +} +``` + +## Authentication Integration + +The NDK instance works with the authentication system to handle signed events: + +```typescript +// Integration with authentication +import { getNDK } from '@/lib/initNDK'; +import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'; + +export async function setupUserAuthentication(privateKey: string): Promise<void> { + try { + // Get NDK instance + const ndk = await getNDK(); + + // Create signer + const signer = new NDKPrivateKeySigner(privateKey); + + // Set signer to enable event signing + ndk.signer = signer; + + console.log('User authentication set up successfully'); + } catch (error) { + console.error('Failed to set up user authentication:', error); + throw error; + } +} +``` + +## Performance Considerations + +### Connection Management + +The implementation handles connection management based on app state to optimize resource usage: + +1. **Connections are established** when the app is in the foreground +2. **Connection state is maintained** during brief background periods +3. **Disconnections can be triggered** during extended background periods to save resources + +### Caching Strategy + +The initialization includes placeholders for caching, which should be implemented to improve performance: + +```typescript +// Enhanced initialization with caching +const ndk = new NDK({ + explicitRelayUrls: POWR_EXPLICIT_RELAYS, + cacheAdapter: new SQLiteAdapter({ + dbName: 'nostr-cache.db', + tableName: 'events', + migrationsPath: './migrations' + }) +}); +``` + +## Error Handling + +The implementation includes comprehensive error handling: + +1. **Initialization failures** are caught and reported +2. **Connection errors** are stored in state for UI feedback +3. **Recovery mechanisms** automatically retry connections when appropriate + +## Testing + +### Unit Tests + +```typescript +describe('NDK Initialization', () => { + beforeEach(() => { + // Reset module for each test + jest.resetModules(); + }); + + it('should initialize NDK with correct configuration', async () => { + // Mock NDK constructor + const mockNDK = jest.fn(); + jest.mock('@nostr-dev-kit/ndk', () => mockNDK); + + // Import module + const { initNDK } = require('../initNDK'); + + // Call initialization + await initNDK(); + + // Verify NDK was constructed with correct options + expect(mockNDK).toHaveBeenCalledWith( + expect.objectContaining({ + explicitRelayUrls: expect.any(Array), + enableOutboxModel: true + }) + ); + }); + + it('should maintain singleton instance across multiple calls', async () => { + // Import module + const { initNDK, getNDK } = require('../initNDK'); + + // Call initialization twice + const instance1 = await initNDK(); + const instance2 = await initNDK(); + + // Get instance directly + const instance3 = await getNDK(); + + // Verify all instances are the same + expect(instance1).toBe(instance2); + expect(instance1).toBe(instance3); + }); +}); +``` + +### Integration Tests + +```typescript +describe('NDK App Integration', () => { + it('should handle app state changes correctly', async () => { + // Import module + const { initNDK, cleanupNDK } = require('../initNDK'); + + // Initialize + const ndk = await initNDK(); + const connectSpy = jest.spyOn(ndk, 'connect'); + const closeSpy = jest.spyOn(ndk.pool, 'close'); + + // Simulate app going to background + mockAppState('background'); + + // Simulate app coming to foreground + mockAppState('active'); + + // Verify reconnect attempt + expect(connectSpy).toHaveBeenCalled(); + + // Cleanup + await cleanupNDK(); + + // Verify disconnect + expect(closeSpy).toHaveBeenCalled(); + }); +}); +``` + +## Future Improvements + +1. **Enhanced Caching** + - Implement robust caching with SQLite for offline capability + - Add cache invalidation strategies based on event types + +2. **Relay Management** + - Implement dynamic relay selection based on performance + - Add support for user-configurable relay lists + +3. **Authentication Enhancement** + - Support multiple authentication methods (NIP-07, HTTP signer, etc.) + - Improve key management security with secure storage + +4. **Resource Optimization** + - Implement smarter background connection management + - Add connection pooling for high-traffic periods + +## Related Documentation + +- [NDK Comprehensive Guide](./comprehensive_guide.md) - Detailed guide to NDK usage +- [NDK Subscription Analysis](./subscription_analysis.md) - Subscription patterns and best practices +- [Nostr Exercise NIP](../nostr/exercise_nip.md) - Technical specification for Nostr workout events +- [Offline Queue](../nostr/offline_queue.md) - Offline-first sync queue implementation diff --git a/docs/technical/ndk/subscription_analysis.md b/docs/technical/ndk/subscription_analysis.md new file mode 100644 index 0000000..127e0d3 --- /dev/null +++ b/docs/technical/ndk/subscription_analysis.md @@ -0,0 +1,370 @@ +# NDK Subscription Analysis + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Nostr Integration, Social Feed, Contact Lists + +## Purpose + +This document analyzes the NDK subscription system, focusing on best practices for managing subscriptions, contact lists, and related event handling. It provides guidelines for reliable implementation in the POWR app, particularly for social features that depend on these subscription mechanisms. + +## NDK Subscription Overview + +Subscriptions in NDK provide a mechanism to retrieve and monitor events from Nostr relays. The subscription system is built around the `NDKSubscription` class, which manages connections to relays, event caching, and event emission. + +### Key Subscription Concepts + +1. **Filters**: Define what events to retrieve (kind, authors, tags, etc.) +2. **Options**: Control subscription behavior (caching, verification, grouping) +3. **Event Handlers**: Process events as they arrive +4. **Cache Integration**: Improve performance and offline capabilities +5. **Relay Management**: Control which relays receive subscription requests + +## Subscription Options + +NDK offers several configuration options to customize subscription behavior: + +```typescript +interface NDKSubscriptionOptions { + // Whether to close subscription when all relays have sent EOSE + closeOnEose?: boolean; + + // How to use the cache + cacheUsage?: NDKSubscriptionCacheUsage; + + // Whether to skip saving events to cache + dontSaveToCache?: boolean; + + // Group similar subscriptions to reduce relay connections + groupable?: boolean; + + // How long to wait before sending grouped subscriptions + groupableDelay?: number; + + // Verification and validation controls + skipVerification?: boolean; + skipValidation?: boolean; + + // Cache-specific filtering options + cacheUnconstrainFilter?: (keyof NDKFilter)[]; +} +``` + +### Cache Usage Modes + +- `CACHE_FIRST`: Try cache before hitting relays (default) +- `PARALLEL`: Query both cache and relays simultaneously +- `ONLY_CACHE`: Only use cached data, don't hit relays +- `ONLY_RELAY`: Skip cache entirely, always query relays + +## Best Practices for Subscriptions + +### 1. Effective Subscription Configuration + +```typescript +const subscriptionOptions = { + closeOnEose: true, // Close when all relays send EOSE + cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST, // Try cache before hitting relays + groupable: true, // Allow grouping similar subscriptions + groupableDelay: 100, // Wait 100ms to group subscriptions + skipVerification: false, // Verify signatures + skipValidation: false // Validate event structure +}; + +const subscription = ndk.subscribe( + { kinds: [1, 3], // Post and contact list events + authors: [userPubkey] }, + subscriptionOptions +); +``` + +### 2. Event Handling + +```typescript +subscription.on('event', (event, relay, sub, fromCache, optimisticPublish) => { + // Process event + + // Check if this is from cache + if (fromCache) { + // Handle cached event (e.g., for initial UI render) + } else { + // Handle live event from relay (e.g., for updates) + } +}); + +subscription.on('eose', (sub) => { + // End of stored events - all historical data has been received + // Good time to hide loading indicators +}); + +subscription.on('close', (sub) => { + // Subscription closed - clean up any resources +}); +``` + +### 3. Subscription Lifecycle Management + +```typescript +// Start the subscription +subscription.start(); + +// When no longer needed +subscription.stop(); +``` + +### 4. Error Handling + +```typescript +subscription.on('error', (error, relay) => { + console.error(`Subscription error from ${relay.url}:`, error); + // Implement fallback strategy +}); + +subscription.on('closed', (relay, reason) => { + console.log(`Relay ${relay.url} closed connection: ${reason}`); + // Potentially reconnect or use alternative relays +}); +``` + +## Contact List Handling + +The `NDKUser` class provides utilities for working with contact lists (kind:3 events). + +### Retrieving Contacts + +```typescript +// Initialize user +const user = new NDKUser({ pubkey: userPubkey }); +user.ndk = ndk; + +// Get contacts as NDKUser objects +const follows = await user.follows(); + +// Get just the pubkeys +const followSet = await user.followSet(); +``` + +### Managing Contacts + +```typescript +// Add a new follow +const newFollow = new NDKUser({ pubkey: followPubkey }); +await user.follow(newFollow); + +// Remove a follow +await user.unfollow(userToUnfollow); + +// Batch multiple operations +const currentFollows = await user.follows(); +await user.follow(newFollow, currentFollows); +await user.follow(anotherFollow, currentFollows); +``` + +## Performance Optimization Patterns + +### 1. Subscription Grouping + +```typescript +// These will be grouped into a single subscription to the relay +// if created within 100ms of each other +const sub1 = ndk.subscribe( + { kinds: [1], authors: [pubkey1] }, + { groupable: true, groupableDelay: 100 } +); + +const sub2 = ndk.subscribe( + { kinds: [1], authors: [pubkey2] }, + { groupable: true, groupableDelay: 100 } +); +``` + +### 2. Progressive Loading + +```typescript +// Initial UI state from cache +const sub = ndk.subscribe( + { kinds: [3], authors: [userPubkey] }, + { cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST } +); + +// Update as data comes in +sub.on('event', (event, relay, subscription, fromCache) => { + if (fromCache) { + // Update UI immediately with cached data + } else { + // Refresh UI with the latest data from relays + } +}); +``` + +### 3. Targeted Subscriptions + +Limit the scope of subscriptions to reduce data load: + +```typescript +// Use since/until filters to limit time range +const recentSub = ndk.subscribe({ + kinds: [1], + authors: [userPubkey], + since: Math.floor(Date.now() / 1000) - 86400 // Last 24 hours +}); + +// Use specific tag filters +const hashtagSub = ndk.subscribe({ + kinds: [1], + "#t": ["workout", "fitness"] +}); +``` + +## Reliability Implementation + +### 1. Contact List Manager Class + +```typescript +class ContactListManager { + private contactList = new Set<string>(); + private subscription; + private lastUpdated = 0; + + constructor(ndk, userPubkey) { + this.subscription = ndk.subscribe({ + kinds: [3], + authors: [userPubkey] + }); + + this.subscription.on('event', this.handleContactListUpdate.bind(this)); + } + + private handleContactListUpdate(event) { + // Only update if this is a newer event + if (event.created_at > this.lastUpdated) { + this.contactList = new Set( + event.tags + .filter(tag => tag[0] === 'p') + .map(tag => tag[1]) + ); + this.lastUpdated = event.created_at; + } + } + + getContacts() { + return this.contactList; + } + + cleanup() { + this.subscription.stop(); + } +} +``` + +### 2. Implementing Retry Logic + +```typescript +async function fetchContactListWithRetry(attempts = 3) { + for (let i = 0; i < attempts; i++) { + try { + const user = new NDKUser({ pubkey: userPubkey }); + user.ndk = ndk; + return await user.followSet(); + } catch (e) { + if (i === attempts - 1) throw e; + await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i))); + } + } +} +``` + +### 3. Custom Cache Implementation + +For optimal performance, implement a custom cache adapter: + +```typescript +const cacheAdapter = { + async query(subscription) { + // Return cached events matching subscription filters + const results = await db.query( + 'SELECT * FROM events WHERE...', + // Convert subscription filters to query + ); + return results.map(row => new NDKEvent(ndk, JSON.parse(row.event))); + }, + + async setEvent(event, filters, relay) { + // Store event in cache + await db.run( + 'INSERT OR REPLACE INTO events VALUES...', + [event.id, JSON.stringify(event), /* other fields */] + ); + } +}; + +ndk.cacheAdapter = cacheAdapter; +``` + +## Application to POWR Social Features + +### Social Feed Implementation + +```typescript +function initializeSocialFeed() { + // Following feed + const followingFeed = ndk.subscribe({ + kinds: [1, 6], // Posts and reposts + "#p": Array.from(contactList) // People the user follows + }, { + closeOnEose: false, // Keep subscription open for updates + cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST + }); + + followingFeed.on('event', (event) => { + // Process feed events + updateFeedUI(event); + }); + + // Global feed (limited to recent events) + const globalFeed = ndk.subscribe({ + kinds: [1, 6], + since: Math.floor(Date.now() / 1000) - 86400 // Last 24 hours + }); + + globalFeed.on('event', (event) => { + // Process global feed events + updateGlobalFeedUI(event); + }); +} +``` + +### Workout Sharing & Discovery + +```typescript +function initializeWorkoutFeed() { + // Workout-specific feed + const workoutFeed = ndk.subscribe({ + kinds: [1301], // Workout records (from NIP-4e) + "#t": ["workout", "fitness"] // Relevant hashtags + }); + + workoutFeed.on('event', (event) => { + // Process workout events + updateWorkoutFeedUI(event); + }); +} +``` + +## Conclusion + +The NDK subscription system provides a powerful foundation for Nostr data management. By following these best practices, the POWR app can implement reliable, efficient data flows for social features, contact lists, and other Nostr-based functionality. + +Key recommendations: +1. Use proper subscription options for each use case +2. Implement effective caching strategies +3. Handle subscription lifecycle properly +4. Use the `NDKUser` class for contact list operations +5. Group similar subscriptions where possible +6. Implement robust error handling and retry logic + +## Related Documentation + +- [NDK Comprehensive Guide](./comprehensive_guide.md) - Complete guide to NDK functionality +- [Nostr Exercise NIP](../nostr/exercise_nip.md) - Specification for workout events +- [Social Architecture](../../features/social/architecture.md) - Overall social feature architecture diff --git a/docs/technical/nostr/encoding_decoding.md b/docs/technical/nostr/encoding_decoding.md new file mode 100644 index 0000000..0410680 --- /dev/null +++ b/docs/technical/nostr/encoding_decoding.md @@ -0,0 +1,332 @@ +# NIP-19 Encoding and Decoding + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** NDK, POWR Packs, Social Features + +## Purpose + +This document provides a comprehensive guide to working with NIP-19 encoding and decoding in NDK, focusing on practical examples and best practices for the POWR app. It covers how to handle various Nostr address formats (npub, note, naddr) and convert between hex and encoded representations. + +## NIP-19 Overview + +NIP-19 defines human-readable, Bech32-encoded identifiers for various Nostr entities: + +- `npub`: Public keys (e.g., `npub1...`) +- `nsec`: Private keys (e.g., `nsec1...`) +- `note`: Event IDs (e.g., `note1...`) +- `nprofile`: Public key with relay information (e.g., `nprofile1...`) +- `nevent`: Event with relay information (e.g., `nevent1...`) +- `naddr`: Parameterized replaceable event coordinates (e.g., `naddr1...`) + +## Core NIP-19 Functions + +NDK implements NIP-19 functionality in the `events/nip19.ts` file. The key functions you'll need are: + +### 1. Decoding NIP-19 Entities + +```typescript +import { nip19 } from '@nostr-dev-kit/ndk'; + +// Decode any NIP-19 entity (naddr, npub, nsec, note, etc.) +function decodeNaddr(naddrString: string) { + try { + const decoded = nip19.decode(naddrString); + + // For naddr specifically, you'll get: + if (decoded.type === 'naddr') { + const { pubkey, kind, identifier } = decoded.data; + + // pubkey is the hex public key of the author + // kind is the event kind (30004 for lists) + // identifier is the 'd' tag value + + console.log('Hex pubkey:', pubkey); + console.log('Event kind:', kind); + console.log('Identifier:', identifier); + + return decoded.data; + } + + return null; + } catch (error) { + console.error('Invalid NIP-19 format:', error); + return null; + } +} +``` + +### 2. Encoding to NIP-19 Formats + +```typescript +// Create an naddr from components +function createNaddr(pubkey: string, kind: number, identifier: string) { + return nip19.naddrEncode({ + pubkey, // Hex pubkey + kind, // Event kind (number) + identifier // The 'd' tag value + }); +} + +// Create an npub from a hex public key +function hexToNpub(hexPubkey: string) { + return nip19.npubEncode(hexPubkey); +} + +// Create a note (event reference) from event ID +function eventIdToNote(eventId: string) { + return nip19.noteEncode(eventId); +} +``` + +### 3. Utility Functions for Hex Keys + +```typescript +// Convert npub to hex pubkey +function npubToHex(npub: string) { + try { + const decoded = nip19.decode(npub); + if (decoded.type === 'npub') { + return decoded.data as string; // This is the hex pubkey + } + return null; + } catch (error) { + console.error('Invalid npub format:', error); + return null; + } +} + +// Check if a string is a valid hex key (pubkey or event id) +function isValidHexKey(hexString: string) { + return /^[0-9a-f]{64}$/i.test(hexString); +} +``` + +## Using NIP-19 Functions with NDK Filters + +Here's how you would use these functions with NDK filters to fetch a POWR Pack from an naddr: + +```typescript +async function fetchPackFromNaddr(naddr: string) { + try { + // Decode the naddr to get event coordinates + const decoded = nip19.decode(naddr); + + if (decoded.type !== 'naddr') { + throw new Error('Not an naddr'); + } + + const { pubkey, kind, identifier } = decoded.data; + + // Ensure it's a list (kind 30004) + if (kind !== 30004) { + throw new Error('Not a NIP-51 list'); + } + + // Create a filter to fetch the specific list event + const filter = { + kinds: [kind], + authors: [pubkey], // Using the hex pubkey from the naddr + '#d': identifier ? [identifier] : undefined, // Using the d-tag if available + }; + + // Fetch the event + const events = await ndk.fetchEvents(filter); + + if (events.size === 0) { + throw new Error('Pack not found'); + } + + // Get the first matching event + return Array.from(events)[0]; + } catch (error) { + console.error('Error fetching pack:', error); + throw error; + } +} +``` + +## Implementing the Complete naddr Workflow for POWR Packs + +Here's a complete example for fetching and processing a POWR Pack from an naddr: + +```typescript +import NDK, { NDKEvent, NDKFilter, nip19 } from '@nostr-dev-kit/ndk'; + +async function fetchAndProcessPOWRPack(naddr: string) { + // 1. Initialize NDK + const ndk = new NDK({ + explicitRelayUrls: [ + 'wss://relay.damus.io', + 'wss://relay.nostr.band' + ] + }); + await ndk.connect(); + + // 2. Decode the naddr + const decoded = nip19.decode(naddr); + if (decoded.type !== 'naddr') { + throw new Error('Invalid naddr format'); + } + + const { pubkey, kind, identifier } = decoded.data; + + // 3. Create filter to fetch the pack event + const packFilter: NDKFilter = { + kinds: [kind], + authors: [pubkey], + '#d': identifier ? [identifier] : undefined + }; + + // 4. Fetch the pack event + const packEvents = await ndk.fetchEvents(packFilter); + if (packEvents.size === 0) { + throw new Error('Pack not found'); + } + + const packEvent = Array.from(packEvents)[0]; + + // 5. Extract template and exercise references + const templateRefs: string[] = []; + const exerciseRefs: string[] = []; + + for (const tag of packEvent.tags) { + if (tag[0] === 'a') { + const addressPointer = tag[1]; + // Format is kind:pubkey:d-tag + if (addressPointer.startsWith('33402:')) { // Workout template + templateRefs.push(addressPointer); + } else if (addressPointer.startsWith('33401:')) { // Exercise + exerciseRefs.push(addressPointer); + } + } + } + + // 6. Fetch templates and exercises + const templates = await fetchReferencedEvents(ndk, templateRefs); + const exercises = await fetchReferencedEvents(ndk, exerciseRefs); + + // 7. Return the complete pack data + return { + pack: packEvent, + templates, + exercises + }; +} + +// Helper function to fetch events from address pointers +async function fetchReferencedEvents(ndk: NDK, addressPointers: string[]) { + const events: NDKEvent[] = []; + + for (const pointer of addressPointers) { + // Parse the pointer (kind:pubkey:d-tag) + const [kindStr, hexPubkey, dTag] = pointer.split(':'); + const kind = parseInt(kindStr); + + // Create a filter to find this specific event + const filter: NDKFilter = { + kinds: [kind], + authors: [hexPubkey] + }; + + if (dTag) { + filter['#d'] = [dTag]; + } + + // Fetch the events + const fetchedEvents = await ndk.fetchEvents(filter); + events.push(...Array.from(fetchedEvents)); + } + + return events; +} +``` + +## Creating naddr for Sharing Packs + +If you want to generate an naddr that can be shared to allow others to import your POWR Pack: + +```typescript +function createShareableNaddr(packEvent: NDKEvent) { + // Extract the d-tag (identifier) + const dTags = packEvent.getMatchingTags('d'); + const identifier = dTags[0]?.[1] || ''; + + // Create the naddr + const naddr = nip19.naddrEncode({ + pubkey: packEvent.pubkey, + kind: packEvent.kind, + identifier + }); + + return naddr; +} +``` + +## Integration with User Profile Functions + +When displaying user information in the social feed or profile pages, you'll often need to convert between hex and NIP-19 formats: + +```typescript +// Display a user's profile with proper npub format +function displayUserProfile(hexPubkey: string) { + // Convert hex pubkey to npub for display + const npub = nip19.npubEncode(hexPubkey); + + // Format for display (e.g., npub1abc...xyz) + const shortNpub = `${npub.substring(0, 8)}...${npub.substring(npub.length - 3)}`; + + return { + displayName: shortNpub, + fullNpub: npub, + hexPubkey: hexPubkey + }; +} + +// Create nprofile for deep linking to a user profile +function createProfileLink(user: NDKUser) { + // Get the relay URLs the user is known to use + const relayUrls = user.relayUrls || []; + + // Create an nprofile (includes relays) + const nprofile = nip19.nprofileEncode({ + pubkey: user.pubkey, + relays: relayUrls + }); + + return nprofile; +} +``` + +## Best Practices for Working with NIP-19 Formats + +1. **Always validate decoded values**: Check that the decoded data is of the expected type and has the necessary properties. + +2. **Handle encoding/decoding errors**: These functions can throw exceptions if the input is malformed. + +3. **Normalize hex keys**: Convert to lowercase for consistency in filters and comparisons. + +4. **Check event kinds**: Verify that the decoded event kind matches what you expect (30004 for NIP-51 lists). + +5. **Use strong typing**: TypeScript's type system can help catch errors with NIP-19 data. + +6. **Centralize encoding/decoding logic**: Create utility functions rather than scattering encoding/decoding calls throughout your codebase. + +7. **Cache decoded values**: If you're repeatedly working with the same NIP-19 strings, consider caching the decoded results. + +## Common Pitfalls + +1. **Confusing hex and encoded formats**: Always be clear about which format (hex or NIP-19 encoded) a function expects. + +2. **Missing error handling**: NIP-19 functions can throw errors when inputs are invalid. + +3. **Relay selection for naddrEncode**: When creating naddr or nevent, consider which relays to include for better discovery. + +4. **Performance implications**: Encoding/decoding operations are relatively expensive, so avoid doing them in tight loops. + +## Related Documentation + +- [NDK Comprehensive Guide](../ndk/comprehensive_guide.md) - Complete guide to NDK functionality +- [NDK Subscription Analysis](../ndk/subscription_analysis.md) - Guide to NDK subscription system +- [Nostr Exercise NIP](./exercise_nip.md) - Specification for workout events +- [POWR Pack Implementation](../../features/powr_packs/implementation_plan.md) - POWR Pack implementation details diff --git a/docs/technical/nostr/exercise_nip.md b/docs/technical/nostr/exercise_nip.md new file mode 100644 index 0000000..7c986cb --- /dev/null +++ b/docs/technical/nostr/exercise_nip.md @@ -0,0 +1,334 @@ +# NIP-4e: Workout Events + +**Last Updated:** 2025-03-26 +**Status:** Draft +**Related To:** Nostr Integration, Workout Data Structure, Social Sharing + +## Purpose + +This document defines the technical specification for storing and sharing workout data through the Nostr protocol. It details the event kinds, data structures, and implementation guidelines for exercise templates, workout templates, and workout records. This specification serves as the foundation for POWR's Nostr integration and ensures consistent, interoperable workout data across the Nostr ecosystem. + +`draft` `optional` + +This specification defines workout events for fitness tracking. These workout events support both planning (templates) and recording (completed activities). + +## Event Kinds + +### Event Kind Selection Rationale + +The event kinds in this NIP follow Nostr protocol conventions: + +- **Exercise and Workout Templates** (33401, 33402) use parameterized replaceable event kinds (30000+) because: + - They represent content that may be updated or improved over time + - The author may want to replace previous versions with improved ones + - They need the `d` parameter to distinguish between different templates by the same author + - Multiple versions shouldn't accumulate in clients' storage + +- **Workout Records** (1301) use a standard event kind (0-9999) because: + - They represent a chronological feed of activity that shouldn't replace previous records + - Each workout is a unique occurrence that adds to a user's history + - Users publish multiple records over time, creating a timeline + - They're conceptually similar to notes (kind 1) but with structured fitness data + +### Exercise Template (kind: 33401) +Defines reusable exercise definitions. These should remain public to enable discovery and sharing. The `content` field contains detailed form instructions and notes. + +#### Format + +The format uses an _addressable event_ of `kind:33401`. + +The `.content` of these events SHOULD be detailed instructions for proper exercise form. It is required but can be an empty string. + +The list of tags are as follows: + +* `d` (required) - universally unique identifier (UUID). Generated by the client creating the exercise template. +* `title` (required) - Exercise name +* `format` (required) - Defines data structure for exercise tracking (possible parameters: `weight`, `reps`, `rpe`, `set_type`) +* `format_units` (required) - Defines units for each parameter (possible formats: "kg", "count", "0-10", "warmup|normal|drop|failure") +* `equipment` (required) - Equipment type (possible values: `barbell`, `dumbbell`, `bodyweight`, `machine`, `cardio`) +* `difficulty` (optional) - Skill level (possible values: `beginner`, `intermediate`, `advanced`) +* `imeta` (optional) - Media metadata for form demonstrations following NIP-92 format +* `t` (optional, repeated) - Hashtags for categorization such as muscle group or body movement (possible values: `chest`, `legs`, `push`, `pull`) + +``` +{ + "id": <32-bytes lowercase hex-encoded SHA-256 of the serialized event data>, + "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, + "created_at": <Unix timestamp in seconds>, + "kind": 33401, + "content": "<detailed form instructions and notes>", + "tags": [ + ["d", "<UUID>"], + ["title", "<exercise name>"], + ["format", "<parameter>", "<parameter>", "<parameter>", "<parameter>"], + ["format_units", "<unit>", "<unit>", "<unit>", "<unit>"], + ["equipment", "<equipment type>"], + ["difficulty", "<skill level>"], + ["imeta", + "url <url to demonstration media>", + "m <media type>", + "dim <dimensions>", + "alt <alt text>" + ], + ["t", "<hashtag>"], + ["t", "<hashtag>"], + ["t", "<hashtag>"] + ] +} +``` + +### Workout Template (kind: 33402) +Defines a complete workout plan. The `content` field contains workout notes and instructions. Workout templates can prescribe specific parameters while leaving others configurable by the user performing the workout. + +#### Format + +The format uses an _addressable event_ of `kind:33402`. + +The `.content` of these events SHOULD contain workout notes and instructions. It is required but can be an empty string. + +The list of tags are as follows: + +* `d` (required) - universally unique identifier (UUID). Generated by the client creating the workout template. +* `title` (required) - Workout name +* `type` (required) - Type of workout (possible values: `strength`, `circuit`, `emom`, `amrap`) +* `exercise` (required, repeated) - Exercise reference and prescription. Format: ["exercise", "<kind>:<pubkey>:<d-tag>", "<relay-url>", ...parameters matching exercise template format] +* `rounds` (optional) - Number of rounds for repeating formats +* `duration` (optional) - Total workout duration in seconds +* `interval` (optional) - Duration of each exercise portion in seconds (for timed workouts) +* `rest_between_rounds` (optional) - Rest time between rounds in seconds +* `t` (optional, repeated) - Hashtags for categorization + +``` +{ + "id": <32-bytes lowercase hex-encoded SHA-256 of the serialized event data>, + "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, + "created_at": <Unix timestamp in seconds>, + "kind": 33402, + "content": "<workout notes and instructions>", + "tags": [ + ["d", "<UUID>"], + ["title", "<workout name>"], + ["type", "<workout type>"], + ["rounds", "<number of rounds>"], + ["duration", "<duration in seconds>"], + ["interval", "<interval in seconds>"], + ["rest_between_rounds", "<rest time in seconds>"], + ["exercise", "<kind>:<pubkey>:<d-tag>", "<relay-url>", "<param1>", "<param2>", "<param3>", "<param4>"], + ["exercise", "<kind>:<pubkey>:<d-tag>", "<relay-url>", "<param1>", "<param2>", "<param3>", "<param4>"], + ["t", "<hashtag>"], + ["t", "<hashtag>"] + ] +} +``` + +### Workout Record (kind: 1301) +Records a completed workout session. The `content` field contains notes about the workout. + +#### Format + +The format uses a standard event of `kind:1301`. + +The `.content` of these events SHOULD contain notes about the workout experience. It is required but can be an empty string. + +The list of tags are as follows: + +* `d` (required) - universally unique identifier (UUID). Generated by the client creating the workout record. +* `title` (required) - Workout name +* `type` (required) - Type of workout (possible values: `strength`, `circuit`, `emom`, `amrap`) +* `exercise` (required, repeated) - Exercise reference and completion data. Format: ["exercise", "<kind>:<pubkey>:<d-tag>", "<relay-url>", ...parameters matching exercise template format] +* `start` (required) - Unix timestamp in seconds for workout start +* `end` (required) - Unix timestamp in seconds for workout end +* `completed` (required) - Boolean indicating if workout was completed as planned +* `rounds_completed` (optional) - Number of rounds completed +* `interval` (optional) - Duration of each exercise portion in seconds (for timed workouts) +* `template` (optional) - Reference to the workout template used, if any. Format: ["template", "<kind>:<pubkey>:<d-tag>", "<relay-url>"] +* `pr` (optional, repeated) - Personal Record achieved during workout. Format: "<kind>:<pubkey>:<d-tag>,<metric>,<value>" +* `t` (optional, repeated) - Hashtags for categorization + +``` +{ + "id": <32-bytes lowercase hex-encoded SHA-256 of the serialized event data>, + "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, + "created_at": <Unix timestamp in seconds>, + "kind": 1301, + "content": "<workout notes>", + "tags": [ + ["d", "<UUID>"], + ["title", "<workout name>"], + ["type", "<workout type>"], + ["rounds_completed", "<number of rounds completed>"], + ["start", "<Unix timestamp in seconds>"], + ["end", "<Unix timestamp in seconds>"], + + ["exercise", "<kind>:<pubkey>:<d-tag>", "<relay-url>", "<weight>", "<reps>", "<rpe>", "<set_type>"], + ["exercise", "<kind>:<pubkey>:<d-tag>", "<relay-url>", "<weight>", "<reps>", "<rpe>", "<set_type>"], + + ["template", "<kind>:<pubkey>:<d-tag>", "<relay-url>"], + ["pr", "<kind>:<pubkey>:<d-tag>,<metric>,<value>"], + ["completed", "<true/false>"], + ["t", "<hashtag>"], + ["t", "<hashtag>"] + ] +} +``` + +## Exercise Parameters + +### Standard Parameters and Units +* `weight` - Load in kilograms (kg). Empty string for bodyweight exercises, negative values for assisted exercises +* `reps` - Number of repetitions (count) +* `rpe` - Rate of Perceived Exertion (0-10): + - RPE 10: Could not do any more reps, technical failure + - RPE 9: Could maybe do 1 more rep + - RPE 8: Could definitely do 1 more rep, maybe 2 + - RPE 7: Could do 2-3 more reps +* `duration` - Time in seconds +* `set_type` - Set classification (possible values: `warmup`, `normal`, `drop`, `failure`) + +Additional parameters can be defined in exercise templates in the `format_units` tag as needed for specific activities (e.g., distance, heartrate, intensity). + +## Workout Types and Terminology + +This specification provides examples of common workout structures but is not limited to these types. The format is extensible to support various training methodologies while maintaining consistent data structure. + +### Common Workout Types + +#### Strength +Traditional strength training focusing on sets and reps with defined weights. Typically includes warm-up sets, working sets, and may include techniques like drop sets or failure sets. + +#### Circuit +Multiple exercises performed in sequence with minimal rest between exercises and defined rest periods between rounds. Focuses on maintaining work rate through prescribed exercises. + +#### EMOM (Every Minute On the Minute) +Time-based workout where specific exercises are performed at the start of each minute. Rest time is whatever remains in the minute after completing prescribed work. + +#### AMRAP (As Many Rounds/Reps As Possible) +Time-capped workout where the goal is to complete as many rounds or repetitions as possible of prescribed exercises while maintaining proper form. + +## Set Types + +### Normal Sets +Standard working sets that count toward volume and progress tracking. + +### Warm-up Sets +Preparatory sets using submaximal weights. These sets are not counted in metrics or progress tracking. + +### Drop Sets +Sets performed immediately after a working set with reduced weight. These are counted in volume calculations but tracked separately for progress analysis. + +### Failure Sets +Sets where technical failure was reached before completing prescribed reps. These sets are counted in metrics but marked to indicate intensity/failure was reached. + +## Examples + +### Exercise Template +``` +{ + "kind": 33401, + "content": "Stand with feet hip-width apart, barbell over midfoot. Hinge at hips, grip bar outside knees. Flatten back, brace core. Drive through floor, keeping bar close to legs.\n\nForm demonstration: https://powr.me/exercises/deadlift-demo.mp4", + "tags": [ + ["d", "<UUID-deadlift>"], + ["title", "Barbell Deadlift"], + ["format", "weight", "reps", "rpe", "set_type"], + ["format_units", "kg", "count", "0-10", "warmup|normal|drop|failure"], + ["equipment", "barbell"], + ["difficulty", "intermediate"], + ["imeta", + "url https://powr.me/exercises/deadlift-demo.mp4", + "m video/mp4", + "dim 1920x1080", + "alt Demonstration of proper barbell deadlift form" + ], + ["t", "compound"], + ["t", "legs"], + ["t", "posterior"] + ] +} +``` + +### EMOM Workout Template +``` +{ + "kind": 33402, + "content": "20 minute EMOM alternating between squats and deadlifts every 30 seconds. Scale weight as needed to complete all reps within each interval.", + "tags": [ + ["d", "<UUID-emom-template>"], + ["title", "20min Squat/Deadlift EMOM"], + ["type", "emom"], + ["duration", "1200"], + ["rounds", "20"], + ["interval", "30"], + + ["exercise", "33401:<pubkey>:<UUID-squat>", "<relay-url>", "", "5", "7", "normal"], + ["exercise", "33401:<pubkey>:<UUID-deadlift>", "<relay-url>", "", "4", "7", "normal"], + + ["t", "conditioning"], + ["t", "legs"] + ] +} +``` + +### Circuit Workout Record +``` +{ + "kind": 1301, + "content": "Completed first round as prescribed. Second round showed form deterioration on deadlifts.", + "tags": [ + ["d", "<UUID-workout-record>"], + ["title", "Leg Circuit"], + ["type", "circuit"], + ["rounds_completed", "1.5"], + ["start", "1706454000"], + ["end", "1706455800"], + + ["exercise", "33401:<pubkey>:<UUID-squat>", "<relay-url>", "80", "12", "7", "normal"], + ["exercise", "33401:<pubkey>:<UUID-deadlift>", "<relay-url>", "100", "10", "7", "normal"], + + ["exercise", "33401:<pubkey>:<UUID-squat>", "<relay-url>", "80", "12", "8", "normal"], + ["exercise", "33401:<pubkey>:<UUID-deadlift>", "<relay-url>", "100", "4", "10", "failure"], + + ["completed", "false"], + ["t", "legs"] + ] +} +``` + +## Implementation Guidelines + +1. All workout records MUST include accurate start and end times +2. Templates MAY prescribe specific parameters while leaving others as empty strings for user input +3. Records MUST include actual values for all parameters defined in exercise format +4. Failed sets SHOULD be marked with `failure` set_type +5. Records SHOULD be marked as `false` for completed if prescribed work wasn't completed +6. PRs SHOULD only be tracked in workout records, not templates +7. Exercise references MUST use the format "kind:pubkey:d-tag" to ensure proper attribution and versioning + +## POWR App Implementation + +The POWR app implements this NIP specification to provide a comprehensive workout tracking system with social sharing capabilities: + +1. **Local-First Architecture**: All workout data is stored locally first, then optionally published to Nostr relays +2. **Exercise Template Library**: Users can create, share, and discover exercise templates +3. **Workout Planning**: Templates enable consistent workout planning with proper progression +4. **Workout Records**: Completed workouts are recorded with detailed performance metrics +5. **Social Sharing**: Users can share templates and achievements with the Nostr community +6. **Cross-Device Synchronization**: Workout history can be synchronized through Nostr relays + +The implementation follows a three-tier approach to content publishing: +- **Local Only**: Private data stored only on the user's device +- **Publish to Relays**: Data published to Nostr relays but not explicitly shared socially +- **Social Sharing**: Published data with additional social metadata for discovery + +## Related Documentation + +- [Workout Completion Flow](../../features/workout/completion_flow.md) - Implementation of this NIP in the workout completion process +- [NDK Comprehensive Guide](../ndk/comprehensive_guide.md) - Guide to using the Nostr Development Kit +- [Social Architecture](../../features/social/architecture.md) - Architecture for social integration using Nostr +- [Project MVP and Targeted Rebuild](../../project/mvp_and_rebuild.md) - Overall project roadmap and priorities + +## References + +This NIP draws inspiration from: +- [NIP-01: Basic Protocol Flow Description](https://github.com/nostr-protocol/nips/blob/master/01.md) +- [NIP-52: Calendar Events](https://github.com/nostr-protocol/nips/blob/master/52.md) +- [NIP-92: Media Attachments](https://github.com/nostr-protocol/nips/blob/master/92.md#nip-92) diff --git a/docs/technical/nostr/index.md b/docs/technical/nostr/index.md new file mode 100644 index 0000000..01ec471 --- /dev/null +++ b/docs/technical/nostr/index.md @@ -0,0 +1,35 @@ +# Nostr Protocol Documentation + +This section contains technical documentation related to the Nostr protocol implementation in the POWR app. + +## Key Documents + +- [Exercise NIP Specification](./exercise_nip.md) - Nostr Implementation Possibility for exercise and workout data +- [Encoding and Decoding](./encoding_decoding.md) - NIP-19 encoding and decoding processes + +## Nostr Protocol Overview + +The Nostr protocol provides a decentralized social networking protocol that underpins the social features of the POWR app. Key aspects include: + +- Relay-based message distribution +- Public key cryptography for identity +- Event-based data model +- NIP standards for interoperability + +## Implementation in POWR + +Our implementation of Nostr follows these key principles: + +1. Privacy-first approach with user control +2. Offline-first with queued publishing +3. Support for standard and extended event kinds +4. Proper event signing and verification +5. Relay management for optimal connectivity + +## Related Documentation + +- [NDK Implementation](../ndk/index.md) - NDK library used for Nostr interaction +- [Social Architecture](../../features/social/architecture.md) - How Nostr is used in social features +- [Workout Completion Flow](../../features/workout/completion_flow.md) - Workout publishing and sharing via Nostr + +**Last Updated:** 2025-03-25 diff --git a/docs/technical/nostr/offline_queue.md b/docs/technical/nostr/offline_queue.md new file mode 100644 index 0000000..09c61d5 --- /dev/null +++ b/docs/technical/nostr/offline_queue.md @@ -0,0 +1,531 @@ +# Nostr Offline Queue + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Nostr Integration, Offline Support, Publication Queue + +## Purpose + +This document describes the offline queue system for Nostr event publication in the POWR app. It details the implementation of the Publication Queue Service that handles queuing, persistence, and retry of Nostr events when network connectivity is unavailable or unreliable. This system ensures that user-generated content is never lost, even in challenging connectivity scenarios. + +## Overview + +The Publication Queue Service provides a reliable mechanism for publishing Nostr events when network connectivity may be unavailable or unstable. It ensures that users can continue to create and share content regardless of connectivity status, with events being automatically published when connectivity is restored. + +## Architecture + +### Key Components + +1. **PublicationQueueService**: Manages the queue of events pending publication +2. **ConnectivityService**: Monitors network status and notifies when connectivity changes +3. **NostrEventStore**: Persists events in the queue to local storage +4. **PublicationAttemptTracker**: Tracks publication attempts and implements retry strategies + +### Data Flow + +```mermaid +flowchart TD + A[User Action] --> B[Create Nostr Event] + B --> C{Is Online?} + C -->|Yes| D[Attempt Immediate Publication] + C -->|No| E[Add to Queue] + D -->|Success| F[Publication Complete] + D -->|Failure| E + E --> G[Store Event Locally] + H[Network Status Change] --> I{Is Now Online?} + I -->|Yes| J[Process Queue] + J --> K[Publication Attempts] + K -->|Success| L[Remove from Queue] + K -->|Failure| M[Schedule Retry] +``` + +## Implementation Details + +### PublicationQueueService + +The `PublicationQueueService` manages the entire publication process: + +```typescript +export class PublicationQueueService { + private eventStore: NostrEventStore; + private retryStrategy: RetryStrategy; + private connectivityService: ConnectivityService; + private isProcessing = false; + + constructor() { + this.eventStore = new NostrEventStore(); + this.retryStrategy = new ExponentialBackoffRetry(); + this.connectivityService = new ConnectivityService(); + + // Set up connectivity change listeners + this.connectivityService.onConnectivityChange(this.handleConnectivityChange); + } + + /** + * Queue an event for publication + * Attempts immediate publication if online, otherwise stores for later + */ + async queueEvent(event: NostrEvent): Promise<QueueResult> { + try { + // Try immediate publication if online + if (this.connectivityService.isOnline) { + const published = await this.publishEvent(event); + if (published) { + return { success: true, queued: false }; + } + } + + // Store for later if immediate publication failed or offline + await this.eventStore.storeEvent(event); + return { success: true, queued: true }; + } catch (error) { + console.error('Failed to queue event:', error); + return { success: false, error }; + } + } + + /** + * Attempt to publish a single event + */ + private async publishEvent(event: NostrEvent): Promise<boolean> { + try { + await event.publish(); + return true; + } catch (error) { + console.error('Failed to publish event:', error); + return false; + } + } + + /** + * Handle connectivity changes + */ + private handleConnectivityChange = (isOnline: boolean) => { + if (isOnline && !this.isProcessing) { + this.processQueue(); + } + } + + /** + * Process the publication queue + */ + async processQueue(): Promise<void> { + if (this.isProcessing || !this.connectivityService.isOnline) { + return; + } + + try { + this.isProcessing = true; + + // Get all pending events + const pendingEvents = await this.eventStore.getPendingEvents(); + + for (const eventData of pendingEvents) { + try { + // Create NDK event from stored data + const ndk = await getNDK(); + const event = new NDKEvent(ndk); + Object.assign(event, eventData.eventData); + + // Attempt publication + const published = await this.publishEvent(event); + + if (published) { + await this.eventStore.removeEvent(eventData.id); + } else { + // Update attempt count and next retry time + await this.eventStore.updateEventAttempt( + eventData.id, + eventData.attempts + 1, + this.retryStrategy.getNextRetryTime(eventData.attempts + 1) + ); + } + } catch (error) { + console.error(`Failed to process event ${eventData.id}:`, error); + } + } + } finally { + this.isProcessing = false; + } + } + + /** + * Get queue status + */ + async getQueueStatus(): Promise<QueueStatus> { + const pendingEvents = await this.eventStore.getPendingEvents(); + const totalAttempts = pendingEvents.reduce((sum, e) => sum + e.attempts, 0); + + return { + pendingCount: pendingEvents.length, + totalAttempts, + oldestEvent: pendingEvents.length > 0 + ? pendingEvents.reduce((oldest, e) => + e.createdAt < oldest.createdAt ? e : oldest + ).createdAt + : null + }; + } +} +``` + +### NostrEventStore + +The `NostrEventStore` handles persistence of events: + +```typescript +export class NostrEventStore { + private database: SQLiteDatabase; + + constructor() { + this.database = getDatabase(); + this.initializeStore(); + } + + private async initializeStore() { + await this.database.executeSql(` + CREATE TABLE IF NOT EXISTS nostr_event_queue ( + id TEXT PRIMARY KEY, + event_data TEXT NOT NULL, + created_at INTEGER NOT NULL, + attempts INTEGER DEFAULT 0, + next_attempt_at INTEGER, + kind INTEGER NOT NULL, + pubkey TEXT NOT NULL + ); + `); + } + + async storeEvent(event: NostrEvent): Promise<string> { + const id = crypto.randomUUID(); + const now = Date.now(); + + await this.database.executeSql(` + INSERT INTO nostr_event_queue + (id, event_data, created_at, kind, pubkey) + VALUES (?, ?, ?, ?, ?) + `, [ + id, + JSON.stringify(event), + now, + event.kind || 0, + event.pubkey || '' + ]); + + return id; + } + + async getPendingEvents(): Promise<QueuedEvent[]> { + const now = Date.now(); + + const result = await this.database.executeSql(` + SELECT * FROM nostr_event_queue + WHERE next_attempt_at IS NULL OR next_attempt_at <= ? + ORDER BY created_at ASC + `, [now]); + + return result.rows.map(row => ({ + id: row.id, + eventData: JSON.parse(row.event_data), + createdAt: row.created_at, + attempts: row.attempts, + nextAttemptAt: row.next_attempt_at, + kind: row.kind, + pubkey: row.pubkey + })); + } + + async removeEvent(id: string): Promise<void> { + await this.database.executeSql(` + DELETE FROM nostr_event_queue WHERE id = ? + `, [id]); + } + + async updateEventAttempt( + id: string, + attempts: number, + nextAttemptAt: number + ): Promise<void> { + await this.database.executeSql(` + UPDATE nostr_event_queue + SET attempts = ?, next_attempt_at = ? + WHERE id = ? + `, [attempts, nextAttemptAt, id]); + } +} +``` + +### Retry Strategy + +The retry strategy determines timing for publication retries: + +```typescript +interface RetryStrategy { + getNextRetryTime(attempts: number): number; +} + +export class ExponentialBackoffRetry implements RetryStrategy { + private baseDelay: number; + private maxDelay: number; + private jitter: number; + + constructor({ + baseDelay = 5000, // 5 seconds + maxDelay = 3600000, // 1 hour + jitter = 0.2 // 20% randomness + } = {}) { + this.baseDelay = baseDelay; + this.maxDelay = maxDelay; + this.jitter = jitter; + } + + getNextRetryTime(attempts: number): number { + // Exponential backoff with jitter + const exponentialDelay = Math.min( + this.maxDelay, + this.baseDelay * Math.pow(2, attempts - 1) + ); + + // Add jitter to prevent thundering herd problem + const jitterAmount = exponentialDelay * this.jitter; + const jitteredDelay = exponentialDelay + + (Math.random() * 2 - 1) * jitterAmount; + + return Date.now() + jitteredDelay; + } +} +``` + +## Usage in the App + +### Integration with Workout Completion Flow + +The Publication Queue Service is integrated into the workout completion flow to ensure workouts are published reliably: + +```typescript +// In workout completion flow +async function completeWorkout( + workout: Workout, + options: WorkoutCompletionOptions +): Promise<CompletionResult> { + // 1. Save workout locally first (always) + await saveWorkoutLocally(workout); + + // 2. Publish to Nostr if selected + let workoutEventId: string | null = null; + if (options.storageType !== 'local_only') { + // Create workout event + const workoutEvent = createWorkoutEvent(workout, options); + + // Queue for publication (handles online/offline automatically) + const publicationQueue = new PublicationQueueService(); + const result = await publicationQueue.queueEvent(workoutEvent); + + // Track the event ID for social sharing + workoutEventId = workoutEvent.id; + + // If requested, create and queue social share + if (options.shareOnSocial) { + const socialEvent = createSocialShareEvent(workoutEvent, options.socialMessage); + await publicationQueue.queueEvent(socialEvent); + } + } + + // 3. Return result with network status + return { + success: true, + localId: workout.id, + nostrEventId: workoutEventId, + isPendingSync: !navigator.onLine + }; +} +``` + +### Background Sync Process + +The app implements a background sync process that periodically attempts to publish queued events, even when the app is in the background: + +```typescript +// Background sync setup +export function setupBackgroundSync() { + // Set up background task with expo-background-fetch + registerBackgroundFetchAsync(); + + // Also set up foreground periodic sync + const syncInterval = 5 * 60 * 1000; // 5 minutes + setInterval(syncQueuedEvents, syncInterval); +} + +// Sync function +async function syncQueuedEvents() { + try { + const publicationQueue = new PublicationQueueService(); + await publicationQueue.processQueue(); + + // Notify user if relevant + const status = await publicationQueue.getQueueStatus(); + if (status.pendingCount === 0) { + // All synced, update UI if needed + EventEmitter.emit('QUEUE_SYNC_COMPLETE'); + } + } catch (error) { + console.error('Background sync error:', error); + } +} +``` + +## User Experience + +### Offline Indicator + +When events are queued for publication, the app shows a subtle indicator so users know content will be published when connectivity is restored: + +```tsx +function OfflinePublicationIndicator() { + const [queueStatus, setQueueStatus] = useState<QueueStatus | null>(null); + + useEffect(() => { + // Subscribe to queue status updates + const subscription = EventEmitter.addListener( + 'QUEUE_STATUS_CHANGED', + setQueueStatus + ); + + // Initial fetch + updateQueueStatus(); + + return () => subscription.remove(); + }, []); + + async function updateQueueStatus() { + const publicationQueue = new PublicationQueueService(); + const status = await publicationQueue.getQueueStatus(); + setQueueStatus(status); + } + + if (!queueStatus || queueStatus.pendingCount === 0) { + return null; + } + + return ( + <View className="flex-row items-center bg-amber-100 dark:bg-amber-900 px-3 py-1 rounded-full"> + <CloudOffIcon size={16} className="text-amber-600 dark:text-amber-400 mr-2" /> + <Text className="text-xs text-amber-800 dark:text-amber-200"> + {queueStatus.pendingCount === 1 + ? "1 item waiting to publish" + : `${queueStatus.pendingCount} items waiting to publish`} + </Text> + </View> + ); +} +``` + +## Testing Strategy + +### Unit Tests + +```typescript +describe('PublicationQueueService', () => { + let service: PublicationQueueService; + let mockConnectivityService: ConnectivityService; + + beforeEach(() => { + // Mock dependencies + mockConnectivityService = { + isOnline: true, + onConnectivityChange: jest.fn() + }; + + // Inject mocks + service = new PublicationQueueService( + new MockEventStore(), + new MockRetryStrategy(), + mockConnectivityService + ); + }); + + it('should attempt immediate publication when online', async () => { + const mockEvent = new MockNDKEvent(); + const publishSpy = jest.spyOn(mockEvent, 'publish'); + + await service.queueEvent(mockEvent); + + expect(publishSpy).toHaveBeenCalled(); + }); + + it('should queue events when offline', async () => { + mockConnectivityService.isOnline = false; + + const mockEvent = new MockNDKEvent(); + const publishSpy = jest.spyOn(mockEvent, 'publish'); + + await service.queueEvent(mockEvent); + + expect(publishSpy).not.toHaveBeenCalled(); + expect(service.getQueueStatus()).resolves.toEqual( + expect.objectContaining({ pendingCount: 1 }) + ); + }); + + // Additional tests for retry logic, queue processing, etc. +}); +``` + +### Integration Tests + +```typescript +describe('Offline Publication Flow', () => { + it('should complete workout and queue events when offline', async () => { + // Mock offline state + jest.spyOn(navigator, 'onLine', 'get').mockReturnValue(false); + + // Complete a workout + const result = await completeWorkout( + mockWorkout, + { storageType: 'publish_complete', shareOnSocial: true } + ); + + // Verify local storage + expect(LocalStorage.getWorkout(result.localId)).toBeTruthy(); + + // Verify queued for publication + const queue = new PublicationQueueService(); + const status = await queue.getQueueStatus(); + expect(status.pendingCount).toBe(2); // Workout + social share + + // Now simulate going online + jest.spyOn(navigator, 'onLine', 'get').mockReturnValue(true); + + // Process queue + await queue.processQueue(); + + // Verify queue is empty now + const updatedStatus = await queue.getQueueStatus(); + expect(updatedStatus.pendingCount).toBe(0); + }); +}); +``` + +## Future Improvements + +1. **Enhanced Queue Management** + - Priority-based publication (social shares before workout records) + - Dependency-based publication (ensure referenced events publish first) + - User-facing queue management UI + +2. **Improved Network Efficiency** + - Batch publication of related events + - Selective relay targeting based on event type + - Dynamic relay selection based on performance history + +3. **Extended Offline Capabilities** + - Offline reactions and comments on existing content + - Conflict resolution for concurrent edits + - Partial content sync for limited bandwidth scenarios + +## Related Documentation + +- [Nostr Exercise NIP](./exercise_nip.md) - Technical specification for Nostr workout events +- [NDK Comprehensive Guide](../ndk/comprehensive_guide.md) - Guide to using the Nostr Development Kit +- [Workout Completion Flow](../../features/workout/completion_flow.md) - Implementation integration +- [Connectivity Service](../../architecture/networking.md) - Network status monitoring diff --git a/docs/testing/ContactCacheServiceTests.md b/docs/testing/ContactCacheServiceTests.md new file mode 100644 index 0000000..23ce4d5 --- /dev/null +++ b/docs/testing/ContactCacheServiceTests.md @@ -0,0 +1,107 @@ +# Contact Cache Testing Guide + +This document outlines test cases to verify the contact caching system is working correctly. + +## Automated Testing Checks + +Run the following tests to verify the contact caching functionality: + +### Test 1: Initial App Launch (Cold Start) + +**Steps:** +1. Completely close the app +2. Clear any app data (if testing on development) +3. Launch the app +4. Navigate to the Following feed tab +5. Wait for the feed to load + +**Expected Results:** +- You should see a log message: `[SocialFeedService] No contacts available for following feed yet, using fallback` +- Initial content should load from the POWR account as fallback +- In the logs, you should eventually see contacts being loaded and cached: `[useContactList] Cached X contacts for <pubkey>` +- The feed should update after contacts are loaded + +### Test 2: App Restart (Warm Start) + +**Steps:** +1. Restart the app after Test 1 +2. Navigate to the Following feed tab +3. Wait for the feed to load + +**Expected Results:** +- In the logs, you should see: `[useContactList] Loaded X contacts from cache` +- The Following feed should load quickly using cached contacts +- You should NOT see the feed refresh unexpectedly after a few seconds +- The feed should remain stable without unexpected refreshes + +### Test 3: Add/Remove Contacts + +**Steps:** +1. Follow or unfollow a user in the app +2. Navigate back to the Following feed + +**Expected Results:** +- The contact changes should be saved to cache +- The following feed should update to reflect the new contact list +- The feed should not continuously refresh + +### Test 4: Offline Mode + +**Steps:** +1. Load the app and navigate to the Following feed +2. Enable airplane mode or disconnect from the internet +3. Restart the app +4. Navigate to the Following feed + +**Expected Results:** +- The app should load cached contacts +- The feed should show cached content +- You should see offline indicators in the UI + +## Manual Inspection + +For developers, perform these additional checks: + +1. **Database Inspection:** + ```sql + SELECT * FROM contact_cache WHERE owner_pubkey = '<your-test-user-pubkey>'; + ``` + - Verify contacts are being stored in the database + - Check that the cached_at timestamp is updated when refreshing contacts + +2. **Log Analysis:** + - Check the logs to ensure the caching system is working as expected + - Verify there are no errors related to contact caching + - Confirm the refresh logic is not triggering multiple times + +3. **Network Traffic:** + - Monitor network requests to ensure we're not making excessive requests for contacts + - Verify that after initial load, contact requests are less frequent + +## Troubleshooting + +If you encounter issues with the contact caching system: + +1. **Feed Refreshes Unexpectedly:** + - Check if the contacts are being cached correctly + - Verify that the cached contacts are being loaded before attempting to fetch from the network + - Check for error patterns in the logs around the time of the refresh + +2. **No Content in Following Feed:** + - Verify the user is authenticated + - Check if contacts are being fetched properly + - Ensure the fallback to POWR account is working + +3. **Database Issues:** + - Verify the contact_cache table was created + - Check for SQL errors in the logs + - Try clearing the app data and restarting (last resort) + +## Reporting Issues + +When reporting issues, please include: + +1. Full logs showing the flow from app start through to the Following feed +2. Details about when the refresh occurs (if applicable) +3. Steps to reproduce the issue +4. Device and OS information diff --git a/docs/testing/index.md b/docs/testing/index.md new file mode 100644 index 0000000..29dd41e --- /dev/null +++ b/docs/testing/index.md @@ -0,0 +1,29 @@ +# Testing Documentation + +This section contains documentation related to testing the POWR app. + +## Testing Categories + +- [Unit Testing](./unit_testing.md) - Testing individual components and functions +- [Integration Testing](./integration_testing.md) - Testing interactions between components +- [End-to-End Testing](./e2e_testing.md) - Testing complete user flows + +## Implementation Tests + +- [Cache Implementation Testing](./cache_testing.md) - Testing for caching mechanisms +- [Contact Cache Service Tests](./contact_service_tests.md) - Tests for the contact cache service + +## Testing Best Practices + +- Write tests for critical functionality first +- Focus on behavioral testing rather than implementation details +- Use mock data to isolate tests from external dependencies +- Keep tests fast to maintain short feedback loops +- Use snapshot tests for UI components cautiously + +## Related Documentation + +- [Technical Documentation](../technical/index.md) - Technical implementation details +- [Architecture Documentation](../architecture/index.md) - System architecture that influences testing strategy + +**Last Updated:** 2025-03-25 diff --git a/docs/tools/README.md b/docs/tools/README.md new file mode 100644 index 0000000..4a8b31c --- /dev/null +++ b/docs/tools/README.md @@ -0,0 +1,72 @@ +# Documentation Tools + +**Last Updated:** 2025-03-26 +**Status:** Active +**Related To:** Documentation Migration, Quality Assurance + +## Purpose + +This directory contains tools to assist with the documentation process, particularly the migration of documentation from the old structure to the new structure and ensuring documentation quality. + +## Available Tools + +### doc-migrator.js + +A Node.js script to migrate documentation files from the old structure to the new structure with proper formatting and metadata. + +#### Usage + +```bash +node doc-migrator.js <source> <destination> <title> <status> [--dryrun] +``` + +#### Example + +```bash +# Migrate a workout documentation file +node doc-migrator.js docs/design/WorkoutCompletion.md docs/features/workout/completion_flow.md "Workout Completion Flow" "Active" + +# Preview migration without making changes +node doc-migrator.js docs/design/WorkoutCompletion.md docs/features/workout/completion_flow.md "Workout Completion Flow" "Active" --dryrun +``` + +#### Features + +- Adds standard metadata headers (Last Updated, Status, Related To) +- Creates purpose section placeholder +- Adds related documentation section +- Maintains original content +- Updates the migration mapping file with migration status +- Creates necessary directories automatically + +### check-links.js + +A Node.js script to verify that internal links between documentation files are valid and optionally fix broken links based on the migration mapping. + +#### Usage + +```bash +node check-links.js [--fix] +``` + +#### Features + +- Scans all markdown files in the docs directory +- Verifies that relative links point to existing files +- Provides detailed reports of broken links +- Can automatically fix broken links using migration mapping +- Updates migration status and progress statistics +- Ignores external links, anchors, and mailto links + +## Using These Tools in the Migration Process + +1. Use the migration mapping file to identify which files need to be migrated +2. For each file, use `doc-migrator.js` to migrate it to the new location with proper formatting +3. Periodically run `check-links.js` to ensure no broken links are introduced +4. After a batch of migrations, run `check-links.js --fix` to update any links to migrated files + +## Related Documentation + +- [Documentation Organization Plan](../project/documentation/organization_plan.md) +- [Documentation Implementation Script](../project/documentation/implementation_script.md) +- [Documentation Migration Mapping](../project/documentation/migration_mapping.md) diff --git a/docs/tools/check-links.js b/docs/tools/check-links.js new file mode 100644 index 0000000..55189a6 --- /dev/null +++ b/docs/tools/check-links.js @@ -0,0 +1,195 @@ +#!/usr/bin/env node + +/** + * Documentation Link Checker + * + * This script verifies that internal links between documentation files are valid. + * It scans all markdown files in the docs directory and checks that relative links + * point to existing files. + * + * Usage: + * node check-links.js [--fix] + * + * Options: + * --fix Attempt to fix broken links by updating to the new file paths from migration mapping + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +// Parse command line arguments +const args = process.argv.slice(2); +const shouldFix = args.includes('--fix'); + +// Get the docs directory path +const docsRoot = path.resolve(__dirname, '..'); + +// Get all markdown files +console.log('Finding all markdown files...'); +const allMdFiles = execSync(`find ${docsRoot} -name "*.md"`) + .toString() + .split('\n') + .filter(Boolean); + +console.log(`Found ${allMdFiles.length} markdown files to check`); + +// Track all files and links +const allFiles = new Set(allMdFiles.map(f => path.relative(docsRoot, f))); +const brokenLinks = []; +const fixedLinks = []; + +// Optional: Load the migration mapping to attempt to fix broken links +let migrationMap = null; +if (shouldFix) { + try { + const mappingFile = path.join(docsRoot, 'project', 'documentation', 'migration_mapping.md'); + if (fs.existsSync(mappingFile)) { + const mappingContent = fs.readFileSync(mappingFile, 'utf8'); + + // Parse migration mapping table to get old->new path mappings + migrationMap = {}; + const tableRows = mappingContent.match(/\|\s*(?:✅|🔄|⏳|🔀|📁|❌)\s*\|\s*([^\|]+)\s*\|\s*([^\|]+)\s*\|/g); + + if (tableRows) { + tableRows.forEach(row => { + const match = row.match(/\|\s*(?:✅|🔄|⏳|🔀|📁|❌)\s*\|\s*([^\|]+)\s*\|\s*([^\|]+)\s*\|/); + if (match && match[1] && match[2]) { + const oldPath = match[1].trim(); + let newPath = match[2].trim(); + + // Skip archived/deprecated entries (they might not have a new path) + if (newPath !== '📁' && newPath !== '❌' && !newPath.startsWith('🔀')) { + migrationMap[oldPath] = newPath; + } + } + }); + } + + console.log(`Loaded ${Object.keys(migrationMap).length} migration mappings for fixing broken links`); + } + } catch (err) { + console.warn(`Warning: Failed to load migration mapping: ${err.message}`); + console.log('Continuing without auto-fix capability'); + } +} + +// Function to suggest a fix for a broken link +function suggestFix(brokenLink, sourceFileDir) { + if (!migrationMap) return null; + + // Try to resolve the link as if it were pointing to an old file path + const absoluteBrokenPath = path.resolve(sourceFileDir, brokenLink); + const relativeBrokenPath = path.relative(docsRoot, absoluteBrokenPath); + + // Check if the broken link matches any old paths in our migration map + for (const oldPath in migrationMap) { + if (relativeBrokenPath.includes(oldPath) || oldPath.includes(relativeBrokenPath)) { + const newPath = migrationMap[oldPath]; + + // Create a relative link from source file to the new path + const relativeNew = path.relative( + sourceFileDir, + path.join(docsRoot, newPath) + ); + + return relativeNew; + } + } + + return null; +} + +// Check each file for broken links +console.log('Checking links...'); +let totalLinks = 0; + +allMdFiles.forEach(file => { + const content = fs.readFileSync(file, 'utf8'); + const relativeFile = path.relative(docsRoot, file); + const fileDir = path.dirname(file); + + // Find markdown links + const linkRegex = /\[.*?\]\((.*?)\)/g; + let match; + let fileModified = false; + let newContent = content; + + while ((match = linkRegex.exec(content)) !== null) { + const link = match[1]; + totalLinks++; + + // Skip external links, anchors, and mailto links + if (link.startsWith('http') || link.startsWith('#') || link.startsWith('mailto:')) continue; + + // Deal with links that contain anchors + const [linkPath, anchor] = link.split('#'); + + // Resolve relative to the file + const resolvedLink = path.normalize(path.join(fileDir, linkPath)); + const resolvedRelative = path.relative(docsRoot, resolvedLink); + + // Check if file exists + if (!fs.existsSync(resolvedLink)) { + const issue = { + file: relativeFile, + link, + resolvedLink: resolvedRelative, + fullMatch: match[0], + fix: null + }; + + // Try to suggest a fix + if (shouldFix) { + const suggestedFix = suggestFix(linkPath, fileDir); + if (suggestedFix) { + issue.fix = suggestedFix + (anchor ? `#${anchor}` : ''); + + // Replace the link in the content + const originalLink = match[0]; + const fixedLink = originalLink.replace(link, issue.fix); + newContent = newContent.replace(originalLink, fixedLink); + fileModified = true; + + fixedLinks.push(issue); + } + } + + brokenLinks.push(issue); + } + } + + // Save fixed content if needed + if (fileModified) { + fs.writeFileSync(file, newContent); + console.log(`✅ Fixed links in ${relativeFile}`); + } +}); + +// Report results +console.log(`\nChecked ${totalLinks} links in ${allMdFiles.length} files.`); + +if (brokenLinks.length > 0) { + console.log(`\nFound ${brokenLinks.length} broken links:`); + + brokenLinks.forEach(({ file, link, resolvedLink, fix }) => { + console.log(`• In ${file}: Broken link ${link} (resolves to ${resolvedLink})`); + if (fix) { + console.log(` ✓ Fixed to: ${fix}`); + } + }); + + if (shouldFix) { + console.log(`\nAutomatically fixed ${fixedLinks.length} links.`); + if (fixedLinks.length < brokenLinks.length) { + console.log(`${brokenLinks.length - fixedLinks.length} links could not be automatically fixed.`); + } + } else { + console.log('\nTip: Run with --fix to attempt automatic fixes based on migration mapping.'); + } + + process.exit(1); +} else { + console.log('\n✅ All links are valid!'); + process.exit(0); +} diff --git a/docs/tools/doc-migrator.js b/docs/tools/doc-migrator.js new file mode 100644 index 0000000..f46ef2b --- /dev/null +++ b/docs/tools/doc-migrator.js @@ -0,0 +1,149 @@ +#!/usr/bin/env node + +/** + * Documentation Migration Tool + * + * This script facilitates the migration of documentation files from the old structure + * to the new standardized structure outlined in the Documentation Organization Plan. + * + * Usage: + * node doc-migrator.js <source> <destination> <title> <status> [--dryrun] + * + * Example: + * node doc-migrator.js docs/design/WorkoutCompletion.md docs/features/workout/completion_flow.md "Workout Completion Flow" "Active" + * + * Options: + * --dryrun Preview the migration without making any changes + */ + +const fs = require('fs'); +const path = require('path'); + +// Process command line arguments +const args = process.argv.slice(2); +const dryRunIndex = args.indexOf('--dryrun'); +const dryRun = dryRunIndex !== -1; + +if (dryRun) { + args.splice(dryRunIndex, 1); +} + +if (args.length < 4) { + console.error('Usage: node doc-migrator.js <source> <destination> <title> <status> [--dryrun]'); + console.error('Example: node doc-migrator.js docs/design/WorkoutCompletion.md docs/features/workout/completion_flow.md "Workout Completion Flow" "Active"'); + process.exit(1); +} + +const [source, destination, title, status] = args; + +// Validate inputs +if (!fs.existsSync(source)) { + console.error(`Error: Source file '${source}' not found`); + process.exit(1); +} + +// Read source content +console.log(`Reading source file: ${source}`); +const sourceContent = fs.readFileSync(source, 'utf8'); + +// Extract content, removing the original title +const contentWithoutTitle = sourceContent.replace(/^# .+(\r?\n|\r)/, ''); + +// Create new content with template +const today = new Date().toISOString().slice(0, 10); +const newContent = `# ${title} + +**Last Updated:** ${today} +**Status:** ${status} +**Related To:** [Fill in related component] + +## Purpose + +[Brief description of this document's purpose] + +${contentWithoutTitle} + +## Related Documentation + +- [Add related document links] +`; + +// In dry run mode, just show what would be migrated +if (dryRun) { + console.log('\n=== DRY RUN ==='); + console.log(`\nWould create: ${destination}`); + console.log('\nNew content preview:'); + console.log('-------------------'); + console.log(newContent.substring(0, 500) + '...'); + console.log('-------------------'); + console.log('\nNo changes were made (dry run mode)'); + process.exit(0); +} + +// Ensure destination directory exists +const destDir = path.dirname(destination); +if (!fs.existsSync(destDir)) { + console.log(`Creating directory structure: ${destDir}`); + fs.mkdirSync(destDir, { recursive: true }); +} + +// Write new file +console.log(`Writing to destination: ${destination}`); +fs.writeFileSync(destination, newContent); +console.log(`✅ Successfully migrated ${source} to ${destination}`); + +// Create a function to update the migration mapping file +function updateMigrationMapping(source, destination, status = '✅') { + const mappingFile = 'docs/project/documentation/migration_mapping.md'; + + if (!fs.existsSync(mappingFile)) { + console.log(`Warning: Migration mapping file (${mappingFile}) not found. Skipping update.`); + return; + } + + console.log(`Updating migration mapping in ${mappingFile}`); + let mappingContent = fs.readFileSync(mappingFile, 'utf8'); + + // Find the line with the source file path + const sourcePathRegex = new RegExp(`\\|\\s*⏳\\s*\\|\\s*${source.replace(/\//g, '\\/')}\\s*\\|`); + + if (sourcePathRegex.test(mappingContent)) { + // Update the status if found + mappingContent = mappingContent.replace( + sourcePathRegex, + `| ${status} | ${source} |` + ); + + // Update overall progress count + const progressRegex = /\*\*Overall Progress\*\*: (\d+)\/(\d+) \((\d+)%\)/; + const match = progressRegex.exec(mappingContent); + + if (match) { + const completed = parseInt(match[1], 10) + 1; + const total = parseInt(match[2], 10); + const percentage = Math.round((completed / total) * 100); + mappingContent = mappingContent.replace( + progressRegex, + `**Overall Progress**: ${completed}/${total} (${percentage}%)` + ); + } + + fs.writeFileSync(mappingFile, mappingContent); + console.log(`✅ Updated migration mapping file`); + } else { + console.log(`Note: Couldn't find entry for ${source} in mapping file.`); + } +} + +// Update the migration mapping +try { + updateMigrationMapping(source, destination); +} catch (error) { + console.error(`Warning: Failed to update migration mapping: ${error.message}`); +} + +console.log('\nMigration completed successfully!'); +console.log('\nNext steps:'); +console.log('1. Review the migrated document and update the purpose section'); +console.log('2. Add relevant related documentation links'); +console.log('3. Update any cross-references in other documents');