diff --git a/.planning/codebase/ARCHITECTURE.md b/.planning/codebase/ARCHITECTURE.md
new file mode 100644
index 000000000..8f3ad21f0
--- /dev/null
+++ b/.planning/codebase/ARCHITECTURE.md
@@ -0,0 +1,168 @@
+# Architecture
+
+**Analysis Date:** 2026-01-11
+
+## Pattern Overview
+
+**Overall:** Hybrid Microservices with Shared Monolithic Database
+
+**Key Characteristics:**
+- Service-oriented architecture with distinct service boundaries
+- Monolithic Rails backend transitioning to modern React frontend
+- Shared PostgreSQL database across all services
+- Real-time communication via WebSocket gateway with Protocol Buffers
+- Async messaging via RabbitMQ/AMQP between services
+
+## Layers
+
+**Presentation Layer (Frontend):**
+- Purpose: User interface and client-side logic
+- Contains: React 16 SPA with Redux Toolkit state management
+- Location: `jam-ui/src/`
+- Entry Point: `jam-ui/src/index.js` → `Main.js` (providers) → `App.js` (layout router)
+- Depends on: Rails API (`web/`) via REST, WebSocket gateway for real-time
+- Used by: End users via browser at `http://beta.jamkazam.local:4000`
+
+**API Layer (Backend):**
+- Purpose: REST API endpoints, request processing, authentication
+- Contains: Rails 4.2.8 controllers, responders, error handling
+- Location: `web/app/controllers/` (83 API controller files)
+- Base Controller: `api_controller.rb` provides auth, error handling, response formatting
+- Depends on: Shared business logic gem (`ruby/`), PostgreSQL
+- Used by: React frontend, mobile apps, third-party integrations
+
+**Real-time Communication Layer:**
+- Purpose: WebSocket connections for live session updates
+- Contains: EventMachine-based WebSocket server with Protocol Buffer messaging
+- Location: `websocket-gateway/`
+- Protocol: Protocol Buffers (`.proto` definitions in `pb/src/client_container.proto`)
+- Bridge: `jam-ui/src/jamClientProxy.js` manages native C++ client communication via QWebChannel
+- Depends on: Shared Ruby gem, WebSocket protocol
+- Used by: React frontend, native desktop client
+
+**Shared Business Logic Layer:**
+- Purpose: Centralized models, validations, domain logic
+- Contains: ActiveRecord models (217 files), managers, message factory
+- Location: `ruby/lib/jam_ruby/` (shared gem embedded via local path)
+- Key Files: `ruby/lib/jam_ruby/models/` (217 model files), `message_factory.rb`, `connection_manager.rb`
+- Depends on: Rails frameworks, PostgreSQL
+- Used by: All services (web, admin, websocket-gateway)
+
+**Data Layer:**
+- Purpose: Persistent storage
+- Contains: PostgreSQL schema, migrations
+- Location: `db/up/` (403+ migration files)
+- ORM: ActiveRecord models in `ruby/lib/jam_ruby/models/`
+- Depends on: PostgreSQL server
+- Used by: All services via ActiveRecord
+
+## Data Flow
+
+**REST API Request (Example: Fetch User Profile):**
+
+1. User action in React component (e.g., `JKUserProfile.js`)
+2. Redux dispatch or direct API call via `apiFetch()` (`jam-ui/src/helpers/rest.js`)
+3. HTTP request to Rails controller (e.g., `api_users_controller.rb`)
+4. Controller uses shared business logic (`ruby/lib/jam_ruby/models/user.rb`)
+5. ActiveRecord query to PostgreSQL database
+6. Response serialized (RABL template or JSON)
+7. Redux store updated (`jam-ui/src/store/features/*.js`)
+8. Component re-renders with new data
+
+**Real-time Session Flow (Example: Join Music Session):**
+
+1. Native C++ desktop client sends message
+2. Proxied through `jamClientProxy.js` (QWebChannel tunnel to frontend)
+3. WebSocket message to gateway (`websocket-gateway/`)
+4. Protocol Buffer decoding → Rails backend logic
+5. Async processing via Resque jobs or AMQP messages
+6. State changes persisted to PostgreSQL
+7. Broadcast back to all session participants via WebSocket
+8. Protocol Buffer encoding → Frontend updates Redux state
+
+**State Management:**
+- Redux Toolkit for global app state (sessions, tracks, user, notifications)
+- Context API for UI state (native client connection, media player, global settings)
+- WebSocket for real-time state synchronization
+
+## Key Abstractions
+
+**Component (React):**
+- Purpose: UI building blocks with JK prefix for brand-specific components
+- Examples: `JKDashboardMain.js`, `JKSessionModal.js`, `JKJamTrackPlayer.js` in `jam-ui/src/components/`
+- Pattern: Functional components with hooks, prop destructuring, Redux integration
+
+**Hook (React):**
+- Purpose: Reusable stateful logic
+- Examples: `useJamServer.js` (26KB, WebSocket orchestration), `useJamTrack.js`, `useGearUtils.js` in `jam-ui/src/hooks/`
+- Pattern: `use*` naming convention, returns state and handlers
+
+**Controller (Rails):**
+- Purpose: Handle HTTP requests, route to business logic
+- Examples: `api_sessions_controller.rb`, `api_jam_tracks_controller.rb`, `api_users_controller.rb` in `web/app/controllers/`
+- Pattern: RESTful actions, inherit from `api_controller.rb`, use responders
+
+**Model (Rails/ActiveRecord):**
+- Purpose: Domain objects with validations and business rules
+- Examples: `user.rb` (3,078 lines), `music_session.rb` (1,481 lines), `jam_track.rb` in `ruby/lib/jam_ruby/models/`
+- Pattern: ActiveRecord associations, scopes, state machines (AASM)
+
+**API Service Function:**
+- Purpose: Frontend-to-backend communication abstractions
+- Examples: `getSession(id)`, `joinSession(options)`, `updateUser(id, data)` in `jam-ui/src/helpers/rest.js`
+- Pattern: Promise-based, wraps `apiFetch()`, returns resolved/rejected promises
+
+## Entry Points
+
+**Frontend Entry:**
+- Location: `jam-ui/src/index.js`
+- Triggers: Browser loads page at `http://beta.jamkazam.local:4000`
+- Responsibilities: Render React app, wrap with providers (Redux, Context), route to layouts
+
+**Backend API Entry:**
+- Location: `web/config/routes.rb` (51KB route definitions)
+- Triggers: HTTP request to `/api/*` endpoints
+- Responsibilities: Route to controller actions, apply middleware, return JSON responses
+
+**WebSocket Gateway Entry:**
+- Location: `websocket-gateway/` (EventMachine server)
+- Triggers: WebSocket connection from client
+- Responsibilities: Maintain persistent connections, decode/encode Protocol Buffers, broadcast messages
+
+**Shared Library Entry:**
+- Location: `ruby/lib/jam_ruby.rb`
+- Triggers: Required by Rails services
+- Responsibilities: Load all models, managers, and shared utilities
+
+## Error Handling
+
+**Strategy:** Exceptions bubble to top-level handler, logged and returned as JSON errors
+
+**Patterns:**
+- Controllers use `api_controller.rb` base class for centralized error handling
+- Frontend catches promise rejections from `apiFetch()`, displays user-friendly errors
+- Background jobs (Resque) retry on transient failures, log permanent failures
+- WebSocket errors logged server-side, client notified via error messages
+
+## Cross-Cutting Concerns
+
+**Logging:**
+- Rails logger for backend (stdout in development, files in production)
+- Console logging in React frontend (removed in production builds)
+- Bugsnag for error tracking (API key configured in `web/config/application.rb`)
+- InfluxDB for time-series metrics (configured but disabled for production)
+
+**Validation:**
+- ActiveRecord validations in model layer (`ruby/lib/jam_ruby/models/*`)
+- React Hook Form for frontend form validation
+- API parameter validation in controllers
+
+**Authentication:**
+- Devise for user authentication (session-based cookies)
+- OAuth via OmniAuth (Facebook, Google, Twitter)
+- Session sharing between `www.jamkazam.local` (Rails) and `beta.jamkazam.local` (React) via `remember_token` cookie
+
+---
+
+*Architecture analysis: 2026-01-11*
+*Update when major patterns change*
diff --git a/.planning/codebase/CONCERNS.md b/.planning/codebase/CONCERNS.md
new file mode 100644
index 000000000..3c16ae564
--- /dev/null
+++ b/.planning/codebase/CONCERNS.md
@@ -0,0 +1,251 @@
+# Codebase Concerns
+
+**Analysis Date:** 2026-01-11
+
+## Tech Debt
+
+**God Objects - Massive Files:**
+- Issue: Several models and helpers have grown too large, violating single responsibility
+- Files:
+ - `ruby/lib/jam_ruby/jam_track_importer.rb` - 3,617 lines (import logic)
+ - `ruby/lib/jam_ruby/models/user.rb` - 3,078 lines (user model with too many responsibilities)
+ - `ruby/lib/jam_ruby/models/notification.rb` - 1,700 lines
+ - `ruby/lib/jam_ruby/models/music_session.rb` - 1,481 lines
+ - `ruby/lib/jam_ruby/models/lesson_session.rb` - 1,312 lines
+ - `web/app/controllers/api_users_controller.rb` - 1,407 lines
+ - `jam-ui/src/helpers/MixerHelper.js` - 1,270 lines (complex audio mixer logic)
+ - `jam-ui/src/hooks/useMixerHelper_delete.js` - 1,326 lines (appears to be dead code)
+ - `web/app/assets/javascripts/session.js` - 3,345 lines
+ - `web/app/assets/javascripts/jam_rest.js` - 3,227 lines
+- Impact: Hard to maintain, test, understand; high coupling
+- Fix approach: Break into smaller modules by responsibility, extract services/managers
+
+**N+1 Query Problems:**
+- Issue: Commented-out eager loading causing N+1 queries
+- Files:
+ - `ruby/lib/jam_ruby/models/jam_track.rb:387` - "FIXME: n+1 queries for rights and genres"
+ - `ruby/lib/jam_ruby/models/feed.rb:35` - "TODO: SPEED UP QUERY. CURRENTLY TAKES FOR EVER."
+- Impact: Performance degradation under load, slow API responses
+- Fix approach: Uncomment and fix eager loading with `includes()`, add database indexes
+
+**Synchronous Operations (Should Be Async):**
+- Issue: Blocking operations that should run in background jobs
+- Files:
+ - `ruby/lib/jam_ruby/models/notification.rb:82-83` - All notification methods should be async
+ - `ruby/lib/jam_ruby/models/user.rb:1379` - "TODO: make this async" (Notification.send_new_user_follower)
+ - `ruby/lib/jam_ruby/models/user.rb:1392` - "TODO: make this async" (Notification.send_new_band_follower)
+ - `ruby/lib/jam_ruby/models/notification.rb:543` - "TODO: do this in BULK or in async block"
+- Impact: Slow request/response times, poor user experience
+- Fix approach: Move to Resque background jobs, use AMQP for async messaging
+
+**Inefficient In-Memory Operations:**
+- Issue: Loading large datasets into memory instead of counting in database
+- Files:
+ - `ruby/lib/jam_ruby/models/band.rb:74` - "FIXME: this could be a lot of followers; calling size loads all the data into memory"
+ - Multiple methods in `ruby/lib/jam_ruby/models/user.rb` using `.size` instead of `.count()`
+- Impact: High memory usage, potential OOM errors with large datasets
+- Fix approach: Use `.count()` for database counting, avoid loading associations into memory
+
+**Dead/Duplicate Code:**
+- Issue: Legacy files not cleaned up
+- Files:
+ - `jam-ui/src/hooks/useMixerHelper_delete.js` - 1,326 lines (appears to be backup)
+ - `jam-ui/src/sessions/JKLobbyChat copy.js` - Duplicate/backup file
+- Impact: Code confusion, increased maintenance burden
+- Fix approach: Delete dead code, ensure backups via Git history
+
+## Known Bugs
+
+**Race Conditions:**
+- Symptoms: Multiple users completing uploads simultaneously triggers duplicate jobs
+- Trigger: Two users complete recording uploads at same time
+- File: `ruby/lib/jam_ruby/models/recording.rb:685` - "FIXME: There's a possible race condition here. If two users complete uploads at the same time, we'll schedule 2 mixes."
+- Root cause: No atomic check-and-set for upload completion
+- Fix: Add database-level locking or use Redis distributed lock
+
+**Timing Vulnerabilities:**
+- Symptoms: Lesson session scheduling can create past sessions
+- Trigger: Scheduling logic with week offset
+- File: `ruby/lib/jam_ruby/models/lesson_session.rb:1040` - "XXX: week offset as 0? This *could* still be in the past."
+- Root cause: Time calculation doesn't account for current moment
+- Fix: Validate scheduled time is always in future before saving
+
+## Security Considerations
+
+**SQL Injection Vulnerabilities:**
+- Risk: String interpolation in SQL queries allows injection attacks
+- Files:
+ - `ruby/lib/jam_ruby/models/notification.rb:75` - `Notification.delete_all "(session_id = '#{self.session_id}')"`
+ - `ruby/lib/jam_ruby/models/join_request.rb:25` - `JoinRequest.where("join_requests.user_id = '#{current_user.id}'")`
+ - `ruby/lib/jam_ruby/models/user.rb:772` - Multiple string interpolations in where clauses
+ - `ruby/lib/jam_ruby/models/user.rb:1397,1419,1469` - String interpolation in delete_all queries
+ - `ruby/lib/jam_ruby/models/ip_blacklist.rb:11,15` - SQL injection in IP validation
+- Current mitigation: None (direct string interpolation used)
+- Recommendations: Use parameterized queries, ActiveRecord placeholders (?, named parameters)
+
+**Hardcoded Credentials in Source:**
+- Risk: Credentials exposed in version control
+- Files:
+ - `web/Gemfile:17` - `source 'https://jamjam:blueberryjam@int.jamkazam.com/gems/'`
+ - `ruby/Gemfile:4` - Same hardcoded credentials
+- Current mitigation: Private repository (still not secure)
+- Recommendations: Use environment variables, secure credential storage
+
+**HTTP Over HTTPS:**
+- Risk: Unencrypted communication exposes data
+- File: `ruby/lib/jam_ruby/lib/s3_manager.rb:32` - "the client can not support HTTPS atm!!! AGH"
+- Current mitigation: None (explicitly using HTTP)
+- Recommendations: Update client to support HTTPS, enforce SSL/TLS
+
+**Missing Error Handling:**
+- Risk: Unhandled failures expose system internals
+- Files:
+ - `web/lib/google_client.rb:56` - "XXX: what to do when this fails?"
+ - `web/lib/google_client.rb:372` - "TODO: how to test for this:"
+- Current mitigation: None
+- Recommendations: Add proper error handling with user-friendly messages
+
+## Performance Bottlenecks
+
+**Slow Queries:**
+- Problem: Feed query takes excessive time
+- File: `ruby/lib/jam_ruby/models/feed.rb:35` - Marked "CURRENTLY TAKES FOR EVER."
+- Measurement: Not quantified (needs profiling)
+- Cause: Likely missing indexes, complex joins, or N+1 queries
+- Improvement path: Profile query, add indexes, optimize joins, eager load associations
+
+**Memory Leaks:**
+- Problem: AMQP connection manager documented as "leaky"
+- File: `ruby/lib/jam_ruby/amqp/amqp_connection_manager.rb:3` - "It's 'leaky' in that it will give you a AMQP::Channel"
+- Measurement: Not quantified
+- Cause: Channels not properly closed/released
+- Improvement path: Implement proper resource cleanup, use connection pooling
+
+## Fragile Areas
+
+**Complex State Management:**
+- File: `ruby/lib/jam_ruby/models/recorded_track.rb:4`
+- Why fragile: "TODO: use aasm instead of all these bool and bool_was (dirty module) checks"
+- Common failures: State transitions not properly tracked, inconsistent state
+- Safe modification: Migrate to AASM state machine for better transition management
+- Test coverage: Unknown (needs investigation)
+
+**WebSocket Message Handling:**
+- File: `ruby/lib/jam_ruby/message_factory.rb` (42KB)
+- Why fragile: Single massive file handling 100+ message types
+- Common failures: New message types not properly handled, encoding/decoding errors
+- Safe modification: Extract message handlers into separate classes per message type
+- Test coverage: Partial (needs more coverage for edge cases)
+
+## Scaling Limits
+
+**Connection Pooling:**
+- Current capacity: Default PostgreSQL connection pool (5 connections per process)
+- Limit: ~50 concurrent users per web/admin instance before connection exhaustion
+- Symptoms at limit: ActiveRecord::ConnectionTimeoutError, slow responses
+- Scaling path: Increase connection pool size, add connection pooling middleware (PgBouncer)
+
+**File Storage:**
+- Current capacity: AWS S3 (effectively unlimited)
+- Limit: Cost-based rather than technical
+- Symptoms at limit: Increased AWS bills
+- Scaling path: Implement CDN caching, compress media files, archive old content
+
+## Dependencies at Risk
+
+**Ruby Version (Critical):**
+- Risk: Ruby 2.3.1-2.4.1 is severely outdated (current is 3.3+), EOL, security vulnerabilities
+- Impact: Missing security patches, performance improvements, language features
+- Migration plan: Upgrade to Ruby 3.x (requires extensive testing, gem compatibility checks)
+
+**Rails Version (Critical):**
+- Risk: Rails 4.2.8 is EOL (current is 7.x), no security patches
+- Impact: Security vulnerabilities, missing features, incompatible with modern gems
+- Migration plan: Upgrade to Rails 7.x (major breaking changes, extensive refactoring required)
+
+**React Version (Medium):**
+- Risk: React 16.13.1 is two major versions behind (current is 18.x)
+- Impact: Missing concurrent rendering, automatic batching, newer React features
+- Migration plan: Upgrade to React 18 (relatively straightforward, test for breaking changes)
+
+**Nokogiri (Critical):**
+- Risk: nokogiri 1.10.10 has known security vulnerabilities
+- Impact: XML parsing vulnerabilities, potential XSS attacks
+- Migration plan: Update to latest nokogiri version (check for breaking API changes)
+
+**Devise (Medium):**
+- Risk: devise 3.3.0 is very outdated (current is 4.x)
+- Impact: Missing security improvements, authentication features
+- Migration plan: Upgrade to Devise 4.x (check for breaking changes in authentication flow)
+
+## Missing Critical Features
+
+**Email Validation:**
+- Problem: No validation of email quality in user registration
+- File: `ruby/lib/jam_ruby/models/user.rb:1097` - "FIXME: Should verify that the new password meets certain quality criteria"
+- Current workaround: None (accepts any password format)
+- Blocks: Password security compliance, account security
+- Implementation complexity: Low (add password strength validation)
+
+**Model Validations:**
+- Problem: Several validations commented out or marked as TODO
+- Files:
+ - `ruby/lib/jam_ruby/models/rsvp_slot.rb:22` - "TODO: validates :proficiency_level"
+ - `ruby/lib/jam_ruby/models/user_asset.rb:11` - "TODO: validate asset_type"
+- Current workaround: No validation (relies on application-level checks)
+- Blocks: Data integrity, consistent database state
+- Implementation complexity: Low (uncomment and add validations)
+
+**Async Notification System:**
+- Problem: Notifications sent synchronously, blocking request/response
+- Current workaround: Users wait for notifications to send
+- Blocks: Fast API response times, scalability
+- Implementation complexity: Medium (migrate to Resque/Sidekiq jobs)
+
+## Test Coverage Gaps
+
+**Complex Business Logic:**
+- What's not tested: Large portions of User model (3,078 lines), MusicSession lifecycle
+- Risk: Changes break critical user flows without detection
+- Priority: High
+- Difficulty to test: Medium (requires extensive fixtures, complex setup)
+
+**WebSocket Gateway:**
+- What's not tested: Full Protocol Buffer message flow end-to-end
+- Risk: Real-time features break silently
+- Priority: High
+- Difficulty to test: High (requires WebSocket test infrastructure)
+
+**Legacy JavaScript:**
+- What's not tested: Many legacy JavaScript files lack tests
+- Files: `web/app/assets/javascripts/session.js` (3,345 lines), `jam_rest.js` (3,227 lines)
+- Risk: Breaks in production, hard to refactor
+- Priority: Medium
+- Difficulty to test: High (legacy code, no clear boundaries)
+
+## Documentation Gaps
+
+**Complex Helpers:**
+- What's undocumented: `MixerHelper.js` (1,270 lines) has minimal comments
+- File: `jam-ui/src/helpers/MixerHelper.js`
+- Risk: Hard to understand audio mixer logic, difficult to maintain
+- Priority: Medium
+- Fix: Add inline documentation explaining audio concepts, mixer state management
+
+**API Endpoints:**
+- What's undocumented: No centralized API documentation for frontend consumers
+- Risk: Frontend developers don't know available endpoints, parameters
+- Priority: Medium
+- Fix: Generate API docs from Rails routes, use Swagger/OpenAPI
+
+**Debugging Code Left In:**
+- Files:
+ - `jam-ui/src/context/JamClientContext.js:42` - "TODO: Expose jamClient globally for debugging. This is temporarily added. Remove it later."
+ - `jam-ui/src/helpers/MixerHelper.js:9` - "TODO - remove mixMode from MixerHelper?"
+- Impact: Temporary code becomes permanent, clutters codebase
+- Fix: Clean up debugging code before merging to main
+
+---
+
+*Concerns audit: 2026-01-11*
+*Update as issues are fixed or new ones discovered*
diff --git a/.planning/codebase/CONVENTIONS.md b/.planning/codebase/CONVENTIONS.md
new file mode 100644
index 000000000..3db0690f3
--- /dev/null
+++ b/.planning/codebase/CONVENTIONS.md
@@ -0,0 +1,167 @@
+# Coding Conventions
+
+**Analysis Date:** 2026-01-11
+
+## Naming Patterns
+
+**Files:**
+- React components: `JK{ComponentName}.js` (JamKazam brand prefix)
+- Examples: `JKDashboardMain.js`, `JKTimeAgo.js`, `JKSessionModal.js` in `jam-ui/src/components/`
+- Non-JK components: `PascalCase.js` (e.g., `Avatar.js`, `FalconCardHeader.js`)
+- Test files: `*.test.js` (Jest unit tests), `*.spec.ts` (Playwright E2E), `*.spec.rb` (RSpec)
+
+**Functions:**
+- camelCase for all functions: `getPersonById()`, `getUserDetail()`, `createAffiliatePartner()`
+- Examples in `jam-ui/src/helpers/rest.js`
+- Async functions: No special prefix (async/await pattern used)
+- Event handlers: `handle{EventName}` pattern (e.g., `handleClick`, `handleSubmit`)
+
+**Variables:**
+- camelCase for variables: `firstName`, `loadingExperiences`, `classNames`
+- State variables: `const [firstName, setFirstName] = useState('Anthony');`
+- Constants: UPPERCASE convention not strictly enforced, but used for module-level constants
+
+**Types:**
+- Interfaces/Types: PascalCase, no `I` prefix
+- React PropTypes: Imported and used for component prop validation
+- Example from `jam-ui/src/components/common/Avatar.js`: `import PropTypes from 'prop-types';`
+
+## Code Style
+
+**Formatting:**
+- Prettier enforced via `.prettierrc` in `jam-ui/`
+- Single quotes: `true` (use `'string'` not `"string"`)
+- Print width: `120` characters
+- Indentation: 2 spaces (standard Prettier default)
+- Semicolons: Not explicitly configured, follows Prettier defaults
+
+**Linting:**
+- ESLint with `.eslintrc.json` in `jam-ui/`
+- Extends: `react-app`, `prettier`, `plugin:react/recommended`
+- Plugins: `prettier`
+- Rule enforcement: `prettier/prettier` set to `error`
+- Exception: `react/no-unescaped-entities` disabled
+
+## Import Organization
+
+**Order:**
+1. React/core libraries (e.g., `react`, `react-dom`)
+2. Third-party packages (e.g., `prop-types`, `@fortawesome/react-fontawesome`)
+3. Internal/relative imports (e.g., `../../helpers/utils`)
+4. Styles (if applicable)
+
+**Example from `jam-ui/src/components/common/Avatar.js`:**
+```javascript
+import React from 'react';
+import PropTypes from 'prop-types';
+import { isIterableArray } from '../../helpers/utils';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+```
+
+**Grouping:**
+- Blank lines between import groups
+- Alphabetical within groups (not strictly enforced)
+
+**Path Aliases:**
+- No custom path aliases configured
+- Relative imports used throughout (`../../`, `../`)
+
+## Error Handling
+
+**Patterns:**
+- Promise-based error handling with `.catch()` or `try/catch` for async/await
+- Rails backend: Exceptions bubble to `api_controller.rb` base class
+- Frontend: Errors caught in `apiFetch()` wrapper, returned as rejected promises
+- Background jobs: Resque retry logic for transient failures
+
+**Error Types:**
+- API errors: Returned as JSON with status codes
+- Client errors: Logged to console (development), reported to Bugsnag (production)
+- Validation errors: Handled by ActiveRecord validations, returned in API responses
+
+## Logging
+
+**Framework:**
+- Rails logger for backend (configured in `web/config/environments/`)
+- Console logging for frontend (`console.log`, `console.error`)
+- Bugsnag for production error tracking (API key in `web/config/application.rb`)
+
+**Patterns:**
+- Log at controller/action boundaries in Rails
+- Minimal logging in frontend (removed in production builds)
+- InfluxDB for time-series metrics (configured but disabled for production)
+
+## Comments
+
+**When to Comment:**
+- Explain "why" not "what": Complex business logic, non-obvious algorithms
+- TODO comments: Mark incomplete features or planned improvements
+- FIXME comments: Mark known issues requiring fixes
+- XXX comments: Mark questionable code requiring review
+
+**JSDoc/PropTypes:**
+- PropTypes used for React component prop validation
+- JSDoc not extensively used, code is generally self-documenting
+- Test files have header comments describing purpose
+
+**TODO Comments:**
+- Format: `// TODO: description` or `# TODO: description`
+- Examples: `// TODO: Expose jamClient globally for debugging. This is temporarily added. Remove it later.`
+- No username or issue tracking in TODO comments
+
+## Function Design
+
+**Size:**
+- Keep functions under 100 lines where practical
+- Extract complex logic into helper functions
+- Example: Large helpers like `MixerHelper.js` (1,270 lines) contain multiple related functions
+
+**Parameters:**
+- Destructure object parameters in function signature
+- Example: `const Avatar = ({ size, rounded, src, name, emoji, className }) => { ... }`
+- Options objects for 4+ parameters: `function update(id, options) { ... }`
+
+**Return Values:**
+- Explicit return statements
+- Promise-based returns for async operations
+- Example: `return new Promise((resolve, reject) => { ... })`
+
+## Module Design
+
+**Exports:**
+- Named exports preferred: `export const functionName = () => { ... }`
+- Default exports for React components (not universal)
+- Barrel exports via `index.js` files (not extensively used)
+
+**React Components:**
+- Functional components with hooks (React 16.13.1+)
+- Arrow function syntax: `const Component = (props) => { ... }`
+- PropTypes at bottom of file
+- Example component structure:
+ ```javascript
+ import React from 'react';
+ import PropTypes from 'prop-types';
+
+ const Avatar = ({ size, src, name }) => {
+ return
...
;
+ };
+
+ Avatar.propTypes = {
+ size: PropTypes.string,
+ src: PropTypes.string,
+ name: PropTypes.string
+ };
+
+ export default Avatar;
+ ```
+
+**Ruby/Rails Patterns:**
+- Class-based models inheriting from `ActiveRecord::Base`
+- Controller actions follow RESTful conventions
+- Snake_case for file names, method names, variables
+- CamelCase for class names
+
+---
+
+*Convention analysis: 2026-01-11*
+*Update when patterns change*
diff --git a/.planning/codebase/INTEGRATIONS.md b/.planning/codebase/INTEGRATIONS.md
new file mode 100644
index 000000000..1b2c7f82b
--- /dev/null
+++ b/.planning/codebase/INTEGRATIONS.md
@@ -0,0 +1,261 @@
+# External Integrations
+
+**Analysis Date:** 2026-01-11
+
+## APIs & External Services
+
+**Payment Processing - Stripe:**
+- Service: Stripe - Subscription billing and payments
+- Files: `web/app/controllers/api_stripe_controller.rb`, `web/config/application.rb` (lines 456-463)
+- SDK/Client: stripe gem
+- Auth: API keys in `config.stripe[:publishable_key]` and `config.stripe[:secret_key]`
+- Endpoints used: Checkout sessions, customer portal, webhooks, Stripe Connect
+- OmniAuth: stripe_connect configured for OAuth seller onboarding
+
+**Payment Processing - PayPal:**
+- Service: PayPal - Payment processing
+- Files: `web/app/controllers/api_pay_pal_controller.rb`, `web/config/initializers/paypal.rb`
+- SDK/Client: paypal-sdk-merchant-jk 1.118.1
+- Auth: Username/password/signature configured in `web/config/application.rb`
+- Mode: `sandbox` (upgradeable to production)
+- Features: Express Checkout API
+
+**Subscription Management - Recurly:**
+- Service: Recurly - Subscription billing
+- Files: `web/config/application.rb` (lines 176-179), `web/config/initializers/recurly.rb`
+- SDK/Client: recurly gem 2.19.14 (noted: should upgrade to 3.x)
+- Auth: Private and public API keys configured
+- Frontend: Recurly.js via `REACT_APP_RECURLY_PUBLIC_API_KEY` environment variable
+- Features: Subscription management, tax estimation, webhooks
+- SSL: TLSv1_2 enforced
+
+**Alternative Payment - Braintree:**
+- Service: Braintree - Payment processing
+- Auth: Token configured via `config.braintree_token` (sandbox and production tokens available)
+- Frontend: `REACT_APP_BRAINTREE_TOKEN` environment variable
+- Features: Payment method management
+
+**Email Delivery - AWS SES:**
+- Service: AWS SES - Primary email delivery
+- Files: `web/config/application.rb` (lines 271-277)
+- Connection: SMTP server `email-smtp.us-east-1.amazonaws.com:587`
+- Auth: IAM user `AKIA2SXEHOQFKRW3OWMG` with credentials
+- Integration: ActionMailer SMTP configured
+
+**Email Delivery - SendGrid (Backup):**
+- Service: SendGrid - Email delivery
+- SDK/Client: sendgrid gem 1.2.0, sendgrid_toolkit
+- Auth: Username `jamkazam`, password configured in `web/config/application.rb`
+- Status: Currently using AWS SES as primary
+
+## Data Storage
+
+**Databases:**
+- PostgreSQL - Primary data store
+- Connection: via `DATABASE_URL` or `web/config/database.yml`
+- Client: ActiveRecord ORM 4.2.8
+- Migrations: 403+ migration files in `db/up/`
+- Shared: Single database across all services (web, admin, websocket-gateway)
+
+**File Storage - AWS S3:**
+- Service: AWS S3 - File and media storage
+- SDK/Client: aws-sdk ~1
+- Auth: `AWS_KEY` and `AWS_SECRET` environment variables
+- Region: `us-east-1`
+- Buckets:
+ - `jamkazam-dev` (private)
+ - `jamkazam-dev-public` (public)
+ - `jamkazam-analytics-dev` (analytics)
+ - `jamkazam-jamtracks` (music tracks)
+- CDN: CloudFront distribution `d34f55ppvvtgi3.cloudfront.net`
+- Integration: CarrierWave gem for file uploads
+
+**File Storage - Filestack:**
+- Service: Filestack (FilePicker) - File upload service
+- SDK/Client: filepicker-rails gem 0.1.0
+- Auth: API key `AoskPXootR5OS6iIGegL2z`, secret configured
+- S3 bucket: `jamkazam-dev`
+- Upload directory: `avatars`
+
+**Caching:**
+- Redis - Session storage and job queue
+- Host: `localhost:6379`
+- Client: redis gem 3.3.0/3.3.3
+- Used for: Resque job queue, session caching
+
+**Message Queue:**
+- RabbitMQ/AMQP - Async messaging between services
+- Client: amqp gem 1.0.2/0.9.8
+- Host: `127.0.0.1:5672`
+- Used for: Async message publishing between web, admin, websocket-gateway
+
+## Authentication & Identity
+
+**Auth Provider - Devise:**
+- Service: Devise - User authentication
+- SDK/Client: devise gem 3.3.0
+- Implementation: Session-based cookies with `remember_token`
+- Token storage: httpOnly cookies shared between `www.jamkazam.local` and `beta.jamkazam.local`
+- Session management: Rails session store
+
+**OAuth Integrations - Facebook:**
+- Provider: Facebook OAuth
+- Files: `web/config/application.rb` (lines 282-283), `web/config/initializers/omniauth.rb`
+- Credentials: App ID `468555793186398`, secret configured
+- SDK/Client: omniauth-facebook gem
+- Scopes: email, location
+
+**OAuth Integrations - Google:**
+- Provider: Google OAuth 2.0
+- Files: `web/config/application.rb` (lines 170-174), `web/config/initializers/omniauth.rb`
+- Credentials: Client ID `785931784279-gd0g8on6sc0tuesj7cu763pitaiv2la8.apps.googleusercontent.com`
+- SDK/Client: google-api-client, omniauth-google-oauth2 gems
+- Scopes: youtube, youtube.force-ssl, youtube.upload, email, profile
+- API Key: `AIzaSyCPTPq5PEcl4XWcm7NZ2IGClZlbsiE8JNo` (public server key)
+
+**OAuth Integrations - Twitter:**
+- Provider: Twitter OAuth
+- Files: `web/config/application.rb` (lines 285-287), `web/config/initializers/omniauth.rb`
+- Credentials: App ID `nQj2oEeoJZxECC33tiTuIg`, secret configured
+- SDK/Client: omniauth-twitter, twitter gems
+- Scopes: Write access
+- Public account: `jamkazam`
+
+## Monitoring & Observability
+
+**Error Tracking - Bugsnag:**
+- Service: Bugsnag - Error tracking and reporting
+- SDK/Client: bugsnag gem 5.3.2
+- Auth: API key `fa0e229f687bcb2c8711fcb80223744e`
+- Files: `web/config/initializers/bugsnag.rb`
+- Features: EventMachine error hook integration
+
+**Analytics - Google Analytics:**
+- Service: Google Analytics 4
+- Frontend IDs:
+ - Development: `G-MC9BTWXWY4` (`.env.development.example`)
+ - Production: `G-SPTNJRW7WB` (`.env.production`)
+- Backend: Universal Analytics `UA-44184562-2`
+- SDK/Client: react-ga4 2.1.0
+
+**Time Series Metrics - InfluxDB:**
+- Service: InfluxDB - Metrics storage
+- SDK/Client: influxdb gem 0.3.14, influxdb-rails 0.1.12
+- Database: `development` (configurable)
+- Auth: Username/password `root`/`root`
+- Host: `localhost:8086`
+- Status: Currently disabled for production (`ignored_environments` configured)
+- File: `web/config/initializers/influxdb-rails.rb`
+
+**Application Performance - New Relic:**
+- Service: New Relic - APM
+- SDK/Client: newrelic_rpm gem
+- Deployment: Production environment only
+
+## Search & Indexing
+
+**Search - Elasticsearch:**
+- Service: Elasticsearch - Full-text search
+- SDK/Client: elasticsearch gem
+- Host: `http://support.jamkazam.com:9200`
+- Used for: Music session search, content search across platform
+
+## CI/CD & Deployment
+
+**Hosting:**
+- Not explicitly defined (appears to be self-hosted or cloud infrastructure)
+- Environment vars: Configured via `.env` files and `config/application.rb`
+
+**CI Pipeline:**
+- Jenkins build scripts: `*/jenkins` files in each service directory
+- Build script: Root `./build` script
+
+## Environment Configuration
+
+**Development:**
+- Required env vars: `DATABASE_URL`, `AWS_KEY`, `AWS_SECRET`, API keys for services
+- Secrets location: `.env.local` files (gitignored), `config/application.rb` (hardcoded for dev)
+- Mock/stub services: Sandbox modes for Stripe, PayPal, Recurly, Braintree
+
+**Production:**
+- Secrets management: Environment variables, configuration files
+- Database: Production PostgreSQL with connection pooling
+- Services: Production API keys for all external services
+
+## Webhooks & Callbacks
+
+**Incoming - Stripe:**
+- Endpoint: `/api/webhooks/stripe` (inferred from controller naming)
+- Verification: Signature validation via Stripe SDK
+- Events: payment_intent.succeeded, customer.subscription.updated, etc.
+- File: `web/app/controllers/api_stripe_controller.rb`
+
+**Incoming - PayPal:**
+- Endpoint: Configured in PayPal Express Checkout
+- Verification: PayPal SDK handles IPN verification
+- File: `web/app/controllers/api_pay_pal_controller.rb`
+
+**Incoming - Recurly:**
+- Endpoint: Webhook handlers for subscription events
+- Verification: Recurly signature validation
+- Events: Subscription updates, billing events
+
+## Security & Fraud Prevention
+
+**Browser Fingerprinting - Fingerprint.js:**
+- Service: Fingerprint.js - Device identification
+- SDK/Client: @fingerprintjs/fingerprintjs 4.6.0
+- Used for: Fraud detection, device tracking
+- Expiration: 14 days configured
+
+**Email Validation - Kickbox:**
+- Service: Kickbox - Email verification
+- SDK/Client: kickbox gem
+- Auth: API key `e262991e292dd5ef382c4a69f2b359f718cf267712b8684c9c28d6402ec18965`
+- Features: Email verification, bounce checking
+- Status: `check_bounced_emails = false` (can be enabled)
+
+**reCAPTCHA - Google:**
+- Service: Google reCAPTCHA - Bot protection
+- Frontend: `REACT_APP_SITE_KEY=6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B`
+- Backend: Public key `6Let8dgSAAAAAFheKGWrs6iaq_hIlPOZ2f3Bb56B`, private key configured
+- Used for: User registration, checkout protection
+
+## Support & Community
+
+**Support - Desk.com:**
+- Service: Desk.com - Customer support
+- Files: `web/config/application.rb` (lines 184-192)
+- URL: `https://jamkazam.desk.com`
+- Integration: Multipass SSO for support portal
+- Multipass key: Configured for single sign-on
+
+**Forums - Vanilla Forums:**
+- Service: Vanilla Forums - Community forums
+- Files: `web/config/application.rb` (lines 311-315)
+- URL: `http://forums.jamkazam.com`
+- Integration: JSConnect for forum authentication
+- Client ID: `www`, secret configured
+- Staff badge: `(JamKazam Staff)` postfix
+
+## Video & Conferencing
+
+**WebRTC:**
+- Host: `https://webrtc-demo.jamkazam.com`
+- Features: WebRTC-based peer-to-peer video calls
+- Config: `config.video_conferencing_host`, open room functionality
+
+## Utilities
+
+**Survey Integration:**
+- Service: SurveyMonkey
+- Signup survey: `https://www.surveymonkey.com/r/WVBKLYL`
+
+**Live Chat - Olark:**
+- Service: Olark - Live chat widget
+- Status: Disabled (`config.olark_enabled = false`)
+
+---
+
+*Integration audit: 2026-01-11*
+*Update when adding/removing external services*
diff --git a/.planning/codebase/STACK.md b/.planning/codebase/STACK.md
new file mode 100644
index 000000000..4c779e424
--- /dev/null
+++ b/.planning/codebase/STACK.md
@@ -0,0 +1,118 @@
+# Technology Stack
+
+**Analysis Date:** 2026-01-11
+
+## Languages
+
+**Primary:**
+- Ruby 2.4.1 (pinned for production, with noted upgrade path to 2.5+) - All service Gemfiles
+- JavaScript/React - Frontend application code in `jam-ui/src/`
+- Protocol Buffers (proto3) - Real-time messaging definitions in `pb/src/client_container.proto`
+
+**Secondary:**
+- TypeScript 3.9.10 (dev dependency) - `jam-ui/package.json`, used for type checking
+- Python - Utility scripts in `web/script/py/`
+
+## Runtime
+
+**Environment:**
+- Node.js 14.21.3 - `jam-ui/.nvmrc`
+- Ruby 2.4.1 (production), Ruby 2.3.1 (legacy compatibility) - Multiple service Gemfiles
+- PostgreSQL database server
+
+**Package Manager:**
+- npm - `jam-ui/package.json` (React frontend)
+- Bundler 1.17.3 - Ruby gem management (Gemfile.alt for Silicon/M1 Macs with `MODERN_OS=1` flag)
+- Lockfiles: `package-lock.json` (npm), `Gemfile.lock` (bundler), `Gemfile.alt.lock` (M1 Macs)
+
+## Frameworks
+
+**Core:**
+- Rails 4.2.8 - Backend API framework (`web/Gemfile`, `admin/Gemfile`, `websocket-gateway/Gemfile`)
+- React 16.13.1 - Frontend SPA framework (`jam-ui/package.json`)
+- React Router DOM 5.2.0 - Client-side routing
+- Redux Toolkit 1.6.1 - State management (`jam-ui/src/store/`)
+- EventMachine 1.0.4/1.2.3 - Async I/O for WebSocket gateway
+
+**Testing:**
+- Jest - Unit testing for React (inferred from test patterns)
+- Playwright 1.40.0 - E2E testing (`jam-ui/package.json`, `jam-ui/playwright.config.ts`)
+- RSpec 2.11 - Ruby testing framework (`ruby/Gemfile`, test specs in `*/spec/`)
+- Jasmine - Legacy JavaScript tests (`web/spec/javascripts/karma.conf.js`)
+- Capybara 2.13.0 - Browser automation for Rails tests
+
+**Build/Dev:**
+- React Scripts 3.4.3 (Create React App) - `jam-ui/package.json`
+- Webpack CLI 4.10.0 - Module bundling
+- Gulp 4.0.2 - SCSS compilation (`jam-ui/package.json`)
+- Sprockets 3.6.3 - Rails asset pipeline (all Rails services)
+- Babel - JavaScript transpilation (via React Scripts)
+
+## Key Dependencies
+
+**Critical:**
+- ruby-protocol-buffers 1.2.2 - WebSocket message serialization (all service Gemfiles)
+- Devise 3.3.0 - User authentication (all Rails services)
+- ActiveRecord 4.2.8 - ORM for PostgreSQL (`ruby/Gemfile`)
+- Resque - Background job processing (`ruby/Gemfile`, `web/Gemfile`, `admin/Gemfile`)
+- amqp gem - RabbitMQ messaging (`ruby/Gemfile`, `web/Gemfile`, `websocket-gateway/Gemfile`)
+
+**Infrastructure:**
+- PostgreSQL adapter (pg 0.17.1/0.21.0) - Database connectivity
+- Redis (redis gem 3.3.0/3.3.3) - Job queue and session storage
+- CarrierWave 0.9.0/0.11.2 - File upload handling with S3 support
+- aws-sdk ~1 - AWS S3 integration for file storage
+
+**Frontend UI:**
+- Bootstrap 4.5.3 - CSS framework
+- Reactstrap 8.6.0 - Bootstrap React components
+- Font Awesome 5.15.1 - Icon library
+- Howler.js 2.2.4 - Audio playback
+- Chart.js 2.9.3, ECharts 4.9.0 - Data visualization
+- FullCalendar 5.3.1 - Calendar UI
+- Leaflet 1.7.1 - Maps integration
+- React Hook Form 7.11.1 - Form handling
+- i18next 21.3.3 - Internationalization
+
+**Payment/Billing:**
+- Stripe (via stripe gem) - Payment processing
+- PayPal SDK (paypal-sdk-merchant-jk 1.118.1) - PayPal integration
+- Recurly 2.19.14 - Subscription management
+
+## Configuration
+
+**Environment:**
+- `.env` files for environment-specific config - `jam-ui/.env.development`, `.env.staging`, `.env.production`
+- Rails environments - `web/config/environments/*.rb`
+- Application config - `web/config/application.rb` (534 lines of configuration)
+- Initializers - `web/config/initializers/` (26+ initialization files)
+
+**Build:**
+- `tsconfig.json` - TypeScript compiler options (if TypeScript used)
+- `jam-ui/package.json` - Node dependencies and scripts
+- Gemfiles - Ruby dependencies for each service
+- `.prettierrc` - Code formatting (`jam-ui/`)
+- `.eslintrc.json` - JavaScript linting (`jam-ui/`)
+
+## Platform Requirements
+
+**Development:**
+- macOS/Linux/Windows with Node.js 14.21.3
+- Ruby 2.4.1 (or 2.3.1 for legacy)
+- PostgreSQL server
+- Redis server (for Resque)
+- RabbitMQ server (for AMQP messaging)
+- Local hosts configuration: `www.jamkazam.local` (Rails), `beta.jamkazam.local` (React)
+
+**Production:**
+- Linux server environment
+- PostgreSQL production database
+- Redis for job queuing
+- RabbitMQ for async messaging
+- AWS S3 for file storage with CloudFront CDN
+- WebSocket gateway server with EventMachine
+
+---
+
+*Stack analysis: 2026-01-11*
+*Update after major dependency changes*
diff --git a/.planning/codebase/STRUCTURE.md b/.planning/codebase/STRUCTURE.md
new file mode 100644
index 000000000..5d5352776
--- /dev/null
+++ b/.planning/codebase/STRUCTURE.md
@@ -0,0 +1,195 @@
+# Codebase Structure
+
+**Analysis Date:** 2026-01-11
+
+## Directory Layout
+
+```
+jam-cloud/
+├── jam-ui/ # React SPA frontend (port 4000)
+├── web/ # Rails 4.2.8 API backend (port 3000)
+├── admin/ # Rails admin portal (ActiveAdmin)
+├── websocket-gateway/ # EventMachine WebSocket server
+├── ruby/ # Shared business logic gem (jam_ruby)
+├── pb/ # Protocol Buffer definitions
+├── db/ # PostgreSQL migrations (403+ files)
+├── lambda/ # AWS Lambda functions
+├── monitor/ # Monitoring/observability
+├── wordpress/ # WordPress integration
+├── CLAUDE.md # Development documentation
+└── .planning/ # GSD project planning
+```
+
+## Directory Purposes
+
+**jam-ui/**
+- Purpose: Modern React 16 frontend SPA
+- Contains: React components, hooks, Redux store, helpers, layouts
+- Key files: `src/index.js` (entry), `src/App.js` (root), `package.json` (deps)
+- Subdirectories:
+ - `src/components/` - 47 component directories (auth, client, dashboard, jamtracks, feed, common, etc.)
+ - `src/hooks/` - 41 custom React hooks
+ - `src/helpers/` - 22 utility files (rest.js, apiFetch.js, MixerHelper.js, JamServer.js)
+ - `src/store/` - Redux Toolkit setup with 12 feature slices
+ - `src/layouts/` - 18 layout components
+ - `src/context/` - 18 Context API providers
+
+**web/**
+- Purpose: Rails 4.2.8 REST API backend
+- Contains: Controllers (83 API), models (via ruby gem), views (legacy), mailers
+- Key files: `config/routes.rb` (51KB), `config/application.rb` (534 lines of config)
+- Subdirectories:
+ - `app/controllers/` - 83 API controller files
+ - `app/views/` - 71 view directories (legacy Rails views)
+ - `config/initializers/` - 26 initialization files
+ - `spec/` - RSpec tests
+
+**ruby/**
+- Purpose: Shared business logic gem (jam_ruby)
+- Contains: ActiveRecord models, managers, validators, message factory
+- Key files: `lib/jam_ruby.rb` (gem manifest), `lib/jam_ruby/message_factory.rb` (42KB)
+- Subdirectories:
+ - `lib/jam_ruby/models/` - 217 model files (User, MusicSession, JamTrack, Recording, etc.)
+ - `lib/jam_ruby/lib/` - 29 manager/utility files
+ - `lib/jam_ruby/amqp/` - Message queue handlers
+ - `lib/jam_ruby/resque/` - Background job definitions
+
+**websocket-gateway/**
+- Purpose: Real-time WebSocket communication server
+- Contains: EventMachine-based server, WebSocket protocol handling
+- Key files: Gemfile (dependencies), lib/ (implementation)
+- Subdirectories: `spec/` for RSpec tests
+
+**pb/**
+- Purpose: Protocol Buffer message definitions
+- Contains: `.proto` source files, generated Ruby/JavaScript bindings
+- Key files: `src/client_container.proto` (WebSocket message types)
+- Subdirectories:
+ - `target/ruby/jampb/` - Generated Ruby Protocol Buffer classes
+ - `target/javascript/` - Generated JavaScript bindings
+
+**admin/**
+- Purpose: Rails admin portal with ActiveAdmin
+- Contains: Admin controllers, configurations, views
+- Key files: Gemfile (includes activeadmin), config/
+- Subdirectories: Same structure as Rails web service
+
+**db/**
+- Purpose: Database schema and migrations
+- Contains: 403+ migration files in `up/` directory
+- Key files: `build` (migration runner), `manifest` (migration manifest)
+- Subdirectories: `up/` - All migration SQL/Ruby files
+
+## Key File Locations
+
+**Entry Points:**
+- `jam-ui/src/index.js` - React DOM render entry
+- `jam-ui/public/index.html` - HTML template with ``
+- `web/config/routes.rb` - Rails route definitions (51KB)
+- `web/config/application.rb` - Rails configuration (534 lines)
+- `ruby/lib/jam_ruby.rb` - Shared gem manifest
+
+**Configuration:**
+- `jam-ui/.env.development`, `.env.staging`, `.env.production` - Frontend environment config
+- `jam-ui/.eslintrc.json` - ESLint configuration
+- `jam-ui/.prettierrc` - Prettier formatting rules
+- `jam-ui/package.json` - Node dependencies and scripts
+- `web/config/database.yml` - PostgreSQL connection config
+- `web/config/environments/*.rb` - Rails environment-specific settings
+- `*/Gemfile`, `*/Gemfile.alt` - Ruby gem dependencies (Gemfile.alt for M1 Macs)
+
+**Core Logic:**
+- `jam-ui/src/components/` - All React components
+- `jam-ui/src/helpers/rest.js` - 70+ API operation functions (945 lines)
+- `jam-ui/src/helpers/MixerHelper.js` - Audio mixer logic (1,270 lines)
+- `jam-ui/src/hooks/useJamServer.js` - WebSocket orchestration (26KB)
+- `web/app/controllers/` - All Rails API controllers
+- `ruby/lib/jam_ruby/models/` - All business logic models
+
+**Testing:**
+- `jam-ui/tests/` - React tests (Playwright, Jest)
+- `jam-ui/playwright.config.ts` - Playwright E2E config
+- `web/spec/` - Rails RSpec tests
+- `web/spec/javascripts/` - Legacy Jasmine tests with Karma
+- `ruby/spec/` - Ruby gem RSpec tests
+
+**Documentation:**
+- `CLAUDE.md` - Project documentation for Claude Code
+- `README.md` - Project setup and usage (root and service-level)
+
+## Naming Conventions
+
+**Files:**
+- React components: `JK{ComponentName}.js` (e.g., `JKDashboardMain.js`, `JKSessionModal.js`)
+- Non-JK components: `PascalCase.js` (e.g., `Avatar.js`, `FalconCardHeader.js`)
+- React hooks: `use{Feature}.js` (e.g., `useJamServer.js`, `useGearUtils.js`)
+- Helpers: `camelCase.js` (e.g., `apiFetch.js`, `rest.js`, `utils.js`)
+- Rails controllers: `api_{resource}_controller.rb` (e.g., `api_sessions_controller.rb`)
+- Ruby models: `snake_case.rb` (e.g., `user.rb`, `music_session.rb`, `jam_track.rb`)
+- Test files: `*.test.js` (Jest), `*.spec.js` (Jasmine), `*.spec.rb` (RSpec), `*.spec.ts` (Playwright)
+
+**Directories:**
+- React: kebab-case or camelCase (e.g., `components/`, `dashboard/`, `jamtracks/`)
+- Rails: snake_case (e.g., `app/controllers/`, `config/initializers/`)
+- Ruby: snake_case (e.g., `lib/jam_ruby/models/`)
+
+**Special Patterns:**
+- `index.js` - Barrel exports or entry points
+- `*_controller.rb` - Rails controllers
+- `*_spec.rb` - RSpec test files
+- `*.proto` - Protocol Buffer definitions
+
+## Where to Add New Code
+
+**New React Feature:**
+- Primary code: `jam-ui/src/components/{category}/JK{FeatureName}.js`
+- State management: `jam-ui/src/store/features/{feature}Slice.js`
+- API calls: Add functions to `jam-ui/src/helpers/rest.js`
+- Custom hooks: `jam-ui/src/hooks/use{Feature}.js`
+- Tests: `jam-ui/tests/{category}/{feature}.test.js`
+
+**New API Endpoint:**
+- Controller: `web/app/controllers/api_{resource}_controller.rb`
+- Routes: Add to `web/config/routes.rb`
+- Business logic: `ruby/lib/jam_ruby/models/{model}.rb`
+- Tests: `web/spec/controllers/api_{resource}_controller_spec.rb`
+
+**New Model/Business Logic:**
+- Model definition: `ruby/lib/jam_ruby/models/{model_name}.rb`
+- Tests: `ruby/spec/jam_ruby/models/{model_name}_spec.rb`
+
+**New WebSocket Message Type:**
+- Protocol definition: `pb/src/client_container.proto`
+- Rebuild: `cd pb && ./build`
+- Handler: Add to websocket-gateway or message_factory
+
+**New Background Job:**
+- Job class: `ruby/lib/jam_ruby/resque/{job_name}.rb`
+- Enqueue: Call `Resque.enqueue(JobClass, args)` from models/controllers
+
+## Special Directories
+
+**pb/target/**
+- Purpose: Generated Protocol Buffer bindings (Ruby/JavaScript)
+- Source: Auto-generated by `pb/build` script from `.proto` files
+- Committed: Yes (generated code checked in)
+
+**node_modules/**
+- Purpose: npm dependencies for jam-ui
+- Source: Installed via `npm install`
+- Committed: No (.gitignored)
+
+**vendor/bundle/**
+- Purpose: Bundler-installed Ruby gems
+- Source: Installed via `bundle install`
+- Committed: No (.gitignored)
+
+**.planning/**
+- Purpose: GSD project planning files
+- Source: Created by /gsd:* commands
+- Committed: Yes (project planning state)
+
+---
+
+*Structure analysis: 2026-01-11*
+*Update when directory structure changes*
diff --git a/.planning/codebase/TESTING.md b/.planning/codebase/TESTING.md
new file mode 100644
index 000000000..b14db5939
--- /dev/null
+++ b/.planning/codebase/TESTING.md
@@ -0,0 +1,274 @@
+# Testing Patterns
+
+**Analysis Date:** 2026-01-11
+
+## Test Framework
+
+**Runner:**
+- Playwright 1.40.0 - E2E testing for React app (`jam-ui/package.json`, `jam-ui/playwright.config.ts`)
+- Jest - Unit testing for React (inferred from test file patterns)
+- RSpec 2.11 - Ruby testing framework (`ruby/Gemfile`)
+- Jasmine - Legacy JavaScript tests with Karma runner (`web/spec/javascripts/karma.conf.js`)
+
+**Assertion Library:**
+- Jest `expect` - Unit tests
+- Playwright assertions - E2E tests (`expect(page).toHaveText(...)`)
+- RSpec expectations - Ruby tests (`.should`, `expect { }.to`)
+
+**Run Commands:**
+```bash
+# React tests
+cd jam-ui
+npm test # Playwright E2E tests
+npx cypress run # Cypress tests (alternative)
+
+# Ruby tests
+cd ruby
+bundle exec rspec # Ruby gem tests
+
+cd web
+bundle exec rspec # Rails backend tests
+
+cd websocket-gateway
+bundle exec rspec # WebSocket gateway tests
+```
+
+## Test File Organization
+
+**Location:**
+- React unit tests: `jam-ui/tests/helpers/rest.test.js` (co-located pattern)
+- Playwright E2E: `jam-ui/test/friends.spec.ts` (TypeScript)
+- Legacy JavaScript: `web/spec/javascripts/*.spec.js`
+- Ruby tests: `ruby/spec/jam_ruby/models/*.spec.rb`, `web/spec/controllers/*.spec.rb`
+
+**Naming:**
+- Unit tests: `{module}.test.js` (e.g., `rest.test.js`)
+- E2E tests: `{feature}.spec.ts` (e.g., `friends.spec.ts`)
+- Ruby tests: `{class_name}_spec.rb` (e.g., `text_message_spec.rb`)
+
+**Structure:**
+```
+jam-ui/
+ tests/helpers/rest.test.js # Jest unit tests
+ test/friends.spec.ts # Playwright E2E
+web/
+ spec/
+ controllers/ # Controller specs
+ javascripts/ # Legacy Jasmine tests
+ karma.conf.js # Karma configuration
+ruby/
+ spec/jam_ruby/models/ # Model specs
+```
+
+## Test Structure
+
+**Jest Unit Tests:**
+```javascript
+// jam-ui/tests/helpers/rest.test.js
+describe('rest helpers', () => {
+ test('getPersonById calls correct URL', async () => {
+ const payload = { id: 42, name: 'John' };
+ mockResolved(payload);
+ const result = await rest.getPersonById(42);
+ expectCalledWith('/users/42/profile');
+ expect(result).toEqual(payload);
+ });
+});
+```
+
+**Playwright E2E Tests:**
+```typescript
+// jam-ui/test/friends.spec.ts
+import { test, expect } from '@playwright/test';
+
+test.describe.serial('Friends page', () => {
+ test.use({ storageState: 'test/storageState/user1.json' });
+
+ test('Homepage', async ({ page }) => {
+ await page.goto('/');
+ await expect(page.locator('h1').first()).toHaveText('Dashboard - Home');
+ });
+});
+```
+
+**RSpec Ruby Tests:**
+```ruby
+# ruby/spec/jam_ruby/models/text_message_spec.rb
+describe TextMessage do
+ before do
+ TextMessage.delete_all
+ @target_user = FactoryGirl.create(:user)
+ @msg = TextMessage.new(:target_user_id => @target_user.id)
+ end
+
+ it "should retrieve conversation for both users" do
+ @msg.message = "Test message"
+ @msg.save!
+ messages = TextMessage.index(@target_user.id, @source_user.id)
+ messages.count.should == 1
+ end
+end
+```
+
+**Jasmine Legacy Tests:**
+```javascript
+// web/spec/javascripts/jamserver.spec.js
+describe("JamServer", function() {
+ beforeEach(function() {
+ jamserver = context.JK.JamServer;
+ });
+
+ it("Subscribing to ping should call function", function() {
+ var called = false;
+ jamserver.registerMessageCallback(context.JK.MessageType.PING_ACK, function() {
+ called = true;
+ });
+ expect(called).toBeTruthy();
+ });
+});
+```
+
+**Patterns:**
+- Jest: `describe()` blocks for grouping, `test()` or `it()` for individual tests
+- Playwright: `test.describe()` for grouping, `test()` for individual E2E scenarios
+- RSpec: `describe` blocks, `before` hooks, `it` blocks with expectations
+- Jasmine: `describe()` and `it()` blocks, `beforeEach()` hooks
+
+## Mocking
+
+**Framework:**
+- Jest built-in mocking: `jest.mock()` for module mocking
+- RSpec: FactoryGirl for test data fixtures
+- Manual mocks in test helpers
+
+**Jest Patterns:**
+```javascript
+// jam-ui/tests/helpers/rest.test.js
+// Mock apiFetch module
+const mockResolved = (payload) => {
+ apiFetch.mockResolvedValue(payload);
+};
+
+const expectCalledWith = (url) => {
+ expect(apiFetch).toHaveBeenCalledWith(url);
+};
+```
+
+**RSpec Patterns:**
+```ruby
+# ruby/spec/jam_ruby/models/text_message_spec.rb
+before do
+ @target_user = FactoryGirl.create(:user)
+ @source_user = FactoryGirl.create(:user)
+end
+```
+
+**What to Mock:**
+- External API calls (via `apiFetch()` in frontend)
+- Database queries (via FactoryGirl fixtures in Ruby)
+- Time/dates (when testing time-sensitive logic)
+- WebSocket connections (in unit tests)
+
+**What NOT to Mock:**
+- Pure functions and utilities
+- Internal business logic
+- Simple data transformations
+
+## Fixtures and Factories
+
+**Test Data:**
+- FactoryGirl for Ruby models: `FactoryGirl.create(:user)` in `ruby/spec/`
+- Manual test helpers in JavaScript: `mockResolved()`, `expectCalledWith()` in `jam-ui/tests/helpers/rest.test.js`
+- Playwright storage state: `test.use({ storageState: 'test/storageState/user1.json' })` for authenticated tests
+
+**Location:**
+- JavaScript helpers: `jam-ui/tests/helpers/` (if applicable)
+- Ruby factories: Defined in `ruby/spec/` or `web/spec/` (FactoryGirl configuration)
+- Playwright fixtures: `jam-ui/test/storageState/` (session cookies for auth)
+
+## Coverage
+
+**Requirements:**
+- No enforced coverage targets
+- Coverage tracked for awareness, not enforcement
+- Focus on critical paths (API endpoints, core models, business logic)
+
+**Configuration:**
+- Jest: Built-in coverage via `--coverage` flag
+- RSpec: SimpleCov gem for Ruby code coverage
+- Playwright: No coverage (E2E tests focus on integration, not coverage)
+
+**View Coverage:**
+```bash
+# Jest coverage (if configured)
+cd jam-ui
+npm test -- --coverage
+
+# RSpec coverage
+cd ruby
+bundle exec rspec
+# Check coverage/index.html
+```
+
+## Test Types
+
+**Unit Tests:**
+- Scope: Single function/module in isolation
+- Mocking: Mock all external dependencies (API, database)
+- Speed: Fast (<100ms per test)
+- Examples: `jam-ui/tests/helpers/rest.test.js`, `ruby/spec/jam_ruby/models/*.spec.rb`
+
+**Integration Tests:**
+- Scope: Multiple modules working together
+- Mocking: Mock only external boundaries (network, file system)
+- Examples: Rails controller specs testing controller + model interaction
+
+**E2E Tests:**
+- Framework: Playwright 1.40.0 (primary), Cypress (alternative mentioned in CLAUDE.md)
+- Scope: Full user flows through UI
+- Setup: Uses storage state for authenticated sessions
+- Location: `jam-ui/test/*.spec.ts`
+
+## Common Patterns
+
+**Async Testing:**
+```javascript
+// Jest async/await
+test('handles async operation', async () => {
+ const result = await asyncFunction();
+ expect(result).toBe('expected');
+});
+
+// Playwright async
+test('async page interaction', async ({ page }) => {
+ await page.goto('/');
+ await expect(page.locator('h1')).toHaveText('Title');
+});
+```
+
+**Error Testing:**
+```javascript
+// Jest error testing
+test('throws on invalid input', () => {
+ expect(() => functionCall()).toThrow('error message');
+});
+
+// Async error
+test('rejects on failure', async () => {
+ await expect(asyncCall()).rejects.toThrow('error message');
+});
+```
+
+**Before/After Hooks:**
+- Jest: `beforeEach()`, `afterEach()` for setup/teardown
+- RSpec: `before do ... end`, `after do ... end`
+- Playwright: `test.beforeAll()`, `test.afterAll()` for global setup
+
+**Snapshot Testing:**
+- Not used in this codebase
+- Prefer explicit assertions for clarity
+
+---
+
+*Testing analysis: 2026-01-11*
+*Update when test patterns change*