This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
OwnTube.tv is a simple and portable video client for the PeerTube video streaming platform, built with React Native/Expo and supporting web, iOS, Android, tvOS, and Android TV. The codebase is located in the OwnTube.tv/ directory within this repository.
Tagline: "Your own tube, for Your own content" — emphasizing user autonomy and content ownership.
Key Philosophy: This project aims to democratize video distribution by leveraging PeerTube's decentralized infrastructure. Rather than building a centralized YouTube-like app with many users, OwnTube enables many branded apps (one per content distributor) with relatively few users per application. The result is: "Your videos, your user experience, on your apps!" Each organization maintains their own app store presence, review processes, and content responsibility.
Repository Structure:
- This repo (web-client): Canonical development and testing environment, continuously auto-deployed with vanilla branding
- Branded fork (mykhailodanilenko/web-client - "Misha Tube"): Development fork that uses external customizations mechanism (
CLIENT_CUSTOMIZATIONS_REPO+CLIENT_CUSTOMIZATIONS_FILE) to verify customizations work correctly with swift CI/CD before production branded apps adopt them - Template repo (cust-app-template): GitHub template for creating new branded apps with pre-configured CI/CD workflows and
.customizationsfile - Branded app repos (e.g., cust-app-blender, cust-app-xrtube, cust-app-basspistol): Production delivery mechanism that pulls code + CI/CD from this repo, applies branding via
.customizationsfile, and deploys to their own GitHub Pages, TestFlight, and Google Play with manual push-button releases
All commands should be run from the OwnTube.tv/ directory unless otherwise noted.
- Build (web webpack):
npm run build - Test all:
npm test - Test single file:
npm test -- path/to/file.test.tsx - Start development:
npm start - Web development:
npm run web - Mobile development:
npm run iosornpm run android - TV development (iOS):
export EXPO_TV=1 && npx expo prebuild --clean && npx expo run:ios - TV development (Android):
export EXPO_TV=1 && npx expo prebuild --clean && npx expo run:android- After TV development:
unset EXPO_TV && npx expo prebuild --cleanbefore returning to mobile
- After TV development:
- Lint:
npx eslint . - Format check:
npx prettier --check ./ - Format write:
npx prettier --write ./ - Apply patches:
npm run postinstall(runs patch-package)
- Framework: Expo (React Native) with file-based routing via expo-router
- State Management: TanStack Query (React Query) for server state, React Context for global state (ColorScheme, AppConfig, FullScreenModal), Zustand for some local state
- Data Fetching: Axios with custom error handling, wrapped in React Query hooks
- Navigation: expo-router (file-based routing built on react-navigation)
- Testing: Jest with React Testing Library, tests run against real PeerTube nightly instance
- i18n: react-i18next with locale files in
locales/ - Validation: Zod for runtime type checking and instance config validation
- TV Support: react-native-tvos fork instead of standard React Native
api/: API layer with queries (React Query hooks), axios instance setup, and error handlingqueries/: React Query hooks organized by feature (categories.ts, videos.ts, etc.)axiosInstance.ts: Base API class with axios configurationerrorHandler.ts: Universal error handler with toast notificationshelpers.ts: ContainsgetLocalData()for test data andretry()logic
app/: expo-router file-based routing structure- Route structure:
(home)/<screen>withbackendparameter on all routes _layout.tsx: Main app navigation and providers+html.tsx: Required for static export to GitHub Pages
- Route structure:
components/: React components (shared components inshared/subfolder)contexts/: React Context providers (ColorScheme, AppConfig, FullScreenModal)hooks/: Custom React hooks with co-located testsscreens/: Screen components imported by routes inapp/utils/: Utility functions (time formatting, async storage, etc.)locales/: Translation files in[language-code].jsonformattheme/: Design tokens (colors, typography)__mocks__/: Shared mocks for Jest testspublic/featured-instances.json5: Instance configuration fileinstanceConfigs.ts: Zod schema for instance config validationi18n.ts: i18next setup and configurationmetro.config.js: Metro bundler config with TV-specific source extensionsapp.config.ts: Expo configuration with environment variable support
Backend Parameter Pattern: All routes include a backend URL parameter representing the PeerTube instance hostname. This allows users to be directed to specific instances via deep links (e.g., /video?backend=bar.baz&id=123) regardless of their current instance selection.
Instance Configuration System: Multi-instance support with feature toggles and white-labeling via public/featured-instances.json5. Each instance config specifies hostname, branding, and customizations. Accessed via useInstanceConfig() hook which reads from AppConfigContext. Validated at test-time using Zod schema.
For branded apps, the multi-instance architecture remains functional under the hood but is hidden from users through:
EXPO_PUBLIC_PRIMARY_BACKENDenv var sets the default/primary instancecustomizations.menuHideLeaveButton: truein featured-instances.json5 hides the instance switcher UI- Deep links with different
backendparameters still work (useful for testing and edge cases)
This approach satisfies Apple/Google content review requirements (single-instance experience) while maintaining technical flexibility.
Platform-Specific Components: Use platform extensions (.tv.tsx, .web.tsx, .ios.tsx, .android.tsx) for platform-specific implementations. Metro bundler automatically resolves the correct file. TV builds controlled by EXPO_TV environment variable.
Video Playback: Platform-specific video players—native player for mobile/TV using expo-av, video.js with HLS support for web (required because Chrome/Firefox don't natively support HLS). Custom overlay controls for unified experience.
Error Handling: Multi-level error handling with fullscreen messages for blocking errors (e.g., playlist page failed to load) and inline errors for section failures (e.g., single playlist failed). Toast messages for network status changes.
Data Fetching: All API calls wrapped in React Query hooks with automatic background refetching, caching, and retry logic. Error handling via errorHandler.ts shows toast messages. APIs throw OwnTubeError with status code and message.
Customization Pipeline: Branded apps use GitHub environment variables in the owntube environment to configure branding. Variables with EXPO_PUBLIC_* prefix are loaded at build time from an external git repo specified by CLIENT_CUSTOMIZATIONS_REPO and CLIENT_CUSTOMIZATIONS_FILE. The main web-client repo uses vanilla OwnTube.tv branding without external customizations. See docs/customizations.md for complete details on available customization options.
- Formatting: All code must pass Prettier checks. Run
npx prettier --write ./before submitting PRs. - TypeScript: Use proper types; avoid
any. Use_prefix for unused parameters. Project uses strict mode. - Components: Organize in folders with index.ts export. Use platform-specific extensions (.tv.tsx, .web.tsx, .ios.tsx) when needed.
- State Management: Use React Query for API data. Context for global state (ColorScheme, AppConfig, FullScreenModal).
- Error Handling: Use API error handler for API requests. Show appropriate error messages with retry options.
- Imports: Group in order: React/libraries, project modules, types.
- Testing: Test components with React Testing Library. Mock external dependencies. Use
__mocks__directory for shared mocks. Tests run against real PeerTube nightly instance without mocking API responses. - Naming: PascalCase for components, camelCase for functions/variables. Descriptive names.
- i18n: Always use the
tfunction fromuseTranslationfor text strings. Never hard-code user-facing text. - Customization: Use the instance config system for feature toggles and white-labeling via
public/featured-instances.json5. - Platform Support: Test changes across all platforms: Web (desktop/mobile), iOS, Android, tvOS, Android TV.
- Commit Messages: NEVER mention AI agents, Claude, or AI assistance in commit messages. Write commits as if they were written by a human developer.
- PR Guidelines: Squash changes into descriptive commits. Reference GitHub issues. Sign your commits. Code must pass ESLint and Prettier checks. Request reviews from @mykhailodanilenko, @ar9708, and @mblomdahl.
The OwnTube project uses a distributed architecture with three distinct branding approaches:
Serves as the canonical development environment:
- Continuous auto-deployment to GitHub Pages (web) and app stores (mobile/TV)
- Uses vanilla OwnTube.tv branding (no customizations)
- All feature development and testing happens here
- Main branch should always be production-ready
Repository: mykhailodanilenko/web-client ("Misha Tube")
Purpose: Testing vehicle to verify external customizations mechanism works correctly before production branded apps adopt updates.
How it works:
- Fork of main repo maintaining same CI/CD workflows (continuous auto-deploy)
- Uses environment variables in
owntubeGitHub environment:CLIENT_CUSTOMIZATIONS_REPO: Points to external git repo with customizationsCLIENT_CUSTOMIZATIONS_FILE: Specifies which config file to use
- Pulls customizations from OwnTube-tv/client-customizations
- Deployments: Google Play, TestFlight, Web
Why this approach: Maintains swift development velocity while testing that customizations work as intended. Catches issues before they affect production branded apps that deploy manually on their own schedules.
Repository: OwnTube-tv/cust-app-template
Purpose: GitHub template for creating new production branded apps.
Contains:
- Pre-configured CI/CD workflows (pulled from main repo)
.customizationsfile template with all available options- Assets folder structure for custom branding files
- Use GitHub's "Use this template" button to create new branded app repos
Examples: cust-app-blender, cust-app-xrtube, cust-app-basspistol, cust-app-pitube
Purpose: Production delivery for content distributors with their own branding.
How they work:
- Created from cust-app-template repository
- Pull code and CI/CD workflows from main web-client repo
- Apply organization-specific branding via
.customizationsfile in repo root - Manual workflow_dispatch deployment model (push-button releases)
- Deploy after mainline updates, on their own schedule
- Each maintains their own:
- GitHub Pages deployment with optional custom domain
- TestFlight and Google Play accounts
- App review processes
- Content responsibility and legal entity
External Config (Branded Fork method):
- Uses
CLIENT_CUSTOMIZATIONS_REPO+CLIENT_CUSTOMIZATIONS_FILEenvironment variables - Customizations stored in separate git repository
- Enables swift CI/CD testing of customization mechanism
- Example: Misha Tube (mykhailodanilenko/web-client)
Inline Config (Branded App method):
- Uses
.customizationsfile in repo root - All branding contained within branded app repo
- Manual deployment workflow for production control
- Examples: Blender Tube, XR Tube, Privacy Tube, Basspistol
Both methods support:
- Environment variables with
EXPO_PUBLIC_*prefix configure:- App name, slug, and bundle identifiers
- Icons, splash screens, and branding assets (512x512 icon, 1152x1152 splash, TV-specific sizes)
- Legal entity information (for privacy policy)
- Primary backend instance (
EXPO_PUBLIC_PRIMARY_BACKEND) - PostHog diagnostics configuration
- Feature toggles (hide video site links, hide git details, etc.)
- Assets stored in
/assetsfolder (or external repo), referenced with appropriate paths
EXPO_PUBLIC_PRIMARY_BACKENDsets the default instancecustomizations.menuHideLeaveButton: truein featured-instances.json5 hides instance switcher in UI- Multi-instance architecture remains functional for deep links (note: can be bypassed via URL parameters)
- Satisfies Apple/Google content review requirements
- For production branded apps: Use the cust-app-template repository
- For development/testing: Fork main repo and configure external customizations
- Consult docs/customizations.md and docs/pipeline.md for detailed setup instructions
- Trigger: Automatic on push to
mainbranch, or manual via workflow_dispatch - Deployment Strategy: Continuous deployment (every commit to main is production-ready)
- Platforms Built:
- Web: Static export to GitHub Pages
- iOS & tvOS: Simulator builds + optional TestFlight upload
- Android & Android TV: APK builds + optional Google Play upload
- Build Info: Injects
build-info.jsonwith GitHub actor, commit SHA, timestamp, and web URL
- Trigger: Manual workflow_dispatch only (push-button releases)
- Deployment Strategy: Controlled releases by content distributors
- Customizations: Pulled from external git repo during build
- Infrastructure: Each branded app has separate:
- GitHub Pages environment with optional custom domain
- App Store Connect and Google Play accounts
- Certificate/signing keys in GitHub Secrets
-
owntubeenvironment: Stores sensitive secrets for app builds- Apple API keys, certificates, provisioning profiles
- Android keystore and signing credentials
- Google Play service account JSON
CLIENT_CUSTOMIZATIONS_REPOandCLIENT_CUSTOMIZATIONS_FILE(for branded apps)- PostHog API keys (if using custom analytics)
-
github-pagesenvironment: Stores deployment configurationCUSTOM_DEPLOYMENT_URL(optional, for custom domains)- Deep linking configuration (Android fingerprint, package names, Apple bundle ID)
For custom domain deployments, configure in github-pages environment:
CUSTOM_DEPLOYMENT_URL: Your custom domainANDROID_FINGERPRINT: SHA256 fingerprint from Google Play ConsoleANDROID_PACKAGE: Android package nameAPPLE_BUNDLE_ID: iOS bundle identifierAPPLE_DEVELOPMENT_TEAM: Team ID from App Store Connect
Reference: See docs/pipeline.md for detailed CI/CD setup instructions.
OwnTube uses PostHog for product diagnostics with an opt-out default (suitable for general audiences).
Configuration:
EXPO_PUBLIC_POSTHOG_API_KEY: PostHog project key- Main repo: Uses OwnTube.tv PostHog project
- Branded apps: Can specify their own key or set to
nullto disable
EXPO_PUBLIC_POSTHOG_HOST: PostHog instance URL- US:
https://app.posthog.com(default) - EU:
https://eu.posthog.com - Self-hosted: Your own PostHog instance URL
- US:
User Control:
- Users can opt out via "Opt-out of diagnostics" checkbox in Settings
- If opted out, no diagnostics data is sent
- If
EXPO_PUBLIC_POSTHOG_API_KEYisnull, diagnostics are disabled for all users
Events Captured:
- Backend server changes (instance switching)
- Rate limit errors and HTTP request failures
- Product usage patterns for improvement
Reference: See docs/diagnostics.md for complete details.
The patches/ directory contains temporary fixes for upstream library issues. These patches are applied automatically by patch-package during npm install.
-
@react-native-tvos/config-tv+0.1.1.patch- Issue: Plugin overwrites Android TV launcher icons even when
.webpversions exist - Fix: Adds existence check before copying
.pngicons (prevents overwriting.webpfiles) - Status: Temporary workaround for upstream bug
- Issue: Plugin overwrites Android TV launcher icons even when
-
@react-native-async-storage/async-storage+1.23.1.patch- Issue: Library assumes
windowobject always exists, causing SSR/build errors - Fix: Adds
typeof window !== "undefined"guards to all localStorage operations - Status: Temporary workaround for web platform compatibility
- Issue: Library assumes
- When upgrading dependencies: Check if patches still apply cleanly
- Test if patches are still needed: Try removing patch file and running tests
- Monitor upstream: Check if issues have been fixed in newer versions
- Document new patches: Add entry here when creating new patches with
npx patch-package
Note: Patches are intentionally temporary. If a patch exists for >6 months, consider:
- Contributing fix upstream
- Finding alternative library
- Accepting the issue as permanent (document why)
- Project uses react-native-tvos (0.76.9-0) fork instead of standard React Native for TV support
- Metro bundler extends asset extensions (json, json5, ttf, otf) and prioritizes
.tv.*extensions whenEXPO_TV=1 - Zod is used for validation of instance configurations (
instanceConfigs.ts) and other runtime type checks - Icons use IcoMoon format with custom font (assets/fonts/icomoon.ttf and selection.json)
- Variables with
EXPO_PUBLIC_*prefix are accessible in app.config.ts and throughout the app viaprocess.env - Loaded at build time (not runtime), so changes require rebuild
- Main repo uses defaults defined in app.config.ts
- Branded apps override via external customizations file
- Set
EXPO_TV=1environment variable before runningnpx expo prebuild --clean - After TV development, must clean up:
unset EXPO_TV && npx expo prebuild --cleanbefore mobile development - Failing to clean up causes build errors on non-TV platforms
- TV builds use platform-specific files (
.tv.tsxextensions)
- Build numbers: Generated from UTC timestamp in app.config.ts
- Format:
YYMMDDHHmm(iOS keeps full, Android truncates last digit) - TV builds offset by 20 minutes for Android to avoid conflicts
- Format:
- Build info injection: CI/CD injects
build-info.jsonwith GitHub actor, commit SHA, timestamp, and web URL- Displayed in app settings (unless
EXPO_PUBLIC_HIDE_GIT_DETAILSis set)
- Displayed in app settings (unless
- Static web export: Uses expo-router's static export feature for GitHub Pages deployment
- Requires special handling for routing (see
+html.tsxin app directory) - Avoids 404 errors on page refresh unlike single-page export
- Requires special handling for routing (see
Custom Expo config plugins in plugins/ directory:
- withReleaseSigningConfig.js: Injects Android release signing configuration from environment variables
- fixAndroidChromecastLib.js: Fixes Chromecast library compatibility (enables Jetifier, increases heap size)
- withAndroidNotificationControls.js: Adds foreground service permissions for media playback controls
- Mobile/TV: expo-av (native player) - older but TV-supported
- Web: video.js with HLS.js - required because Chrome/Firefox don't natively support HLS
- Keyboard shortcuts: F (fullscreen), M (mute), arrows (skip 10s), space (play/pause)
- Casting: Google Cast SDK integration for Chromecast support
- All routes accept
backendparameter (PeerTube instance hostname) - Additional parameters vary by route (e.g., video ID, timestamp, playlist ID)
- Custom domains require configuration in GitHub Pages environment
- Associated domains configured in app.config.ts for iOS/Android
- Tests run against real PeerTube nightly instance without mocking API responses
- Ensures compatibility with actual PeerTube behavior
- Shared mocks in
__mocks__/for native modules that can't render in test environment
- GitHub Organization: OwnTube-tv
- Project Website: www.owntube.tv
- Company: OwnTube Nordic AB (Swedish org. number: 559517-7196)
- Location: Stockholm, Sweden
- Website: www.owntube.se
- Contact: hello@owntube.se / +46 730 567 567
- Project Contact: hello@owntube.tv
- Year Founded: 2024
- Process: Use the Forking workflow (see README.md for details)
- PR Requirements: Squash commits, sign commits, reference issues, pass linters
- Reviewers: Request reviews from @mykhailodanilenko, @ar9708, and @mblomdahl
- Getting Involved: Contact @ar9708 for contribution opportunities
The OwnTube-tv organization maintains 15+ repositories including:
- web-client (this repo): Main client codebase (React Native/Expo)
- cust-app-template: Template for creating branded apps
- Branded apps: cust-app-blender, cust-app-xrtube, cust-app-privacytube, cust-app-basspistol
- Infrastructure: peertube-runner (containerized transcoding for Kubernetes)
- Marketing websites:
- www.owntube.tv: Official project website (React/Vite/TypeScript with Tailwind CSS)
- www.owntube.se: OwnTube Nordic AB corporate information page (simple Markdown-to-HTML)
- Both deployed via GitHub Pages
Production examples of apps built with OwnTube:
-
Blender Tube: Videos presenting the evolutions of the 3D creation software, tutorials and animated films supported by the Blender Foundation
- PeerTube instance: video.blender.org
- Repository: cust-app-blender
- Web app: cust-app-blender.owntube.tv
-
XR Tube: The PeerTube platform of Extinction Rebellion
- PeerTube instance: tube.rebellion.global
- Repository: cust-app-xrtube
- Web app: cust-app-xrtube.owntube.tv
-
Privacy Tube: Videos presenting excessive state and corporate surveillance issues and advice on how to increase security and freedom through better privacy
- PeerTube instance: media.privacyinternational.org
- Repository: cust-app-pitube
- Web app: cust-app-pitube.owntube.tv
-
Basspistol: Video platform for the Underground Artists sharing their music under free license
- PeerTube instance: v.basspistol.org
- Repository: cust-app-basspistol
- Web app: cust-app-basspistol.owntube.tv
-
Misha Tube: Development/testing branded fork (not a production app)
- Repository: mykhailodanilenko/web-client (fork with external customizations)
- Customizations: OwnTube-tv/client-customizations
- Android: Google Play
- iOS: TestFlight
- Web: cust-app-mishatube.owntube.tv
- Purpose: Verifies customization mechanism works before production branded apps adopt updates
For more branded app examples, visit owntube.tv.