Merge branch 'stable-with-docs'

This commit is contained in:
DocNR 2025-03-26 20:03:16 -07:00
commit e81012841f
64 changed files with 11207 additions and 2 deletions

View File

@ -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

View File

@ -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<void>;
createAccount: () => Promise<void>;
logout: () => Promise<void>;
}
const useAuthStore = create<AuthState>((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

211
docs/architecture/index.md Normal file
View File

@ -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<Workout[]> {...}
// Get workout by ID
async getById(id: string): Promise<Workout | null> {...}
// Save a workout
async save(workout: Workout): Promise<void> {...}
// Delete a workout
async delete(id: string): Promise<void> {...}
}
```
### 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<void> {
// 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<string> {
// Nostr-specific implementation
}
// Get messages from following
async getFollowingFeed(): Promise<Message[]> {
// 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

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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<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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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": <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
## 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)

48
docs/check-links.js Normal file
View File

@ -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!');
}

View File

@ -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.

View File

@ -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

113
docs/doc-migrator.js Executable file
View File

@ -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 <source> <destination> <title> <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();

View File

@ -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

32
docs/features/index.md Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

17
docs/guides/index.md Normal file
View File

@ -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

25
docs/index.md Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

44
docs/project/index.md Normal file
View File

@ -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.

View File

@ -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

View File

@ -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

23
docs/technical/index.md Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

29
docs/testing/index.md Normal file
View File

@ -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

72
docs/tools/README.md Normal file
View File

@ -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)

195
docs/tools/check-links.js Normal file
View File

@ -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);
}

149
docs/tools/doc-migrator.js Normal file
View File

@ -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');