chore: clean up repo organization
- Standardize on bun: remove package-lock.json, add packageManager field, gitignore non-bun lockfiles, fix stray npm install in android:build:clean - Remove stale build logs and empty dirs (src-tauri/plugins, docs/tickets) - Move android-dev.sh into scripts/ - Consolidate root docs into docs/ (docker/builder under docs/build/); move the architecture overview to docs/architecture/README.md - Extract Requirements Specification from README into docs/requirements.md and slim README down to a project intro + docs index - Fix internal references to the moved files
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
# JellyTau Software Architecture
|
||||
|
||||
This document describes the current architecture of JellyTau, a cross-platform Jellyfin client built with Tauri, SvelteKit, and Rust.
|
||||
|
||||
**Last Updated:** 2026-06-20
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
JellyTau uses a client-server architecture: business logic lives in a comprehensive Rust backend, while a UI-rich Svelte frontend handles presentation and interaction.
|
||||
|
||||
### Architecture Principles
|
||||
|
||||
- **Business Logic in Rust**: Core logic — playback, repository, sync, downloads, connectivity — lives in Rust for performance, reliability, and type safety.
|
||||
- **Presentation in Svelte**: The frontend (~20.5k non-test lines) owns UI, layout, navigation, and interaction state and invokes Rust commands. It is intentionally UI-heavy, **not** a thin wrapper. Largest pieces: components + routes (~14.6k lines), stores (~3.4k), api/services/utils (~2.4k); `VideoPlayer.svelte` alone is ~1.6k lines.
|
||||
- **Events + Polling hybrid**: Rust emits events the frontend listens to, and the UI also polls status on short intervals in a few hot spots (e.g. queue status in `library/+layout.svelte`, playback progress in `VideoPlayer.svelte`).
|
||||
- **Handle-Based Resources**: UUID handles for stateful Rust objects.
|
||||
- **Cache-First**: Parallel queries with intelligent fallback.
|
||||
- **Poison-tolerant locking**: Shared `std::sync` state is accessed via the `MutexSafe`/`RwLockSafe` helpers in `utils/lock.rs`, which recover a poisoned lock instead of cascading a panic across the player.
|
||||
- **Graceful backend init**: If a native player backend (MPV/ExoPlayer) fails to initialize, the app falls back to a no-op backend and emits a `backend-init-failed` event rather than crashing.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Frontend["Svelte Frontend"]
|
||||
subgraph Stores["Stores (Thin Wrappers)"]
|
||||
auth["auth"]
|
||||
player["player"]
|
||||
queue["queue"]
|
||||
library["library"]
|
||||
connectivity["connectivity"]
|
||||
playbackMode["playbackMode"]
|
||||
end
|
||||
subgraph Components
|
||||
playerComp["player/"]
|
||||
libraryComp["library/"]
|
||||
Search["Search"]
|
||||
end
|
||||
subgraph Routes
|
||||
routeLibrary["/library"]
|
||||
routePlayer["/player"]
|
||||
routeRoot["/"]
|
||||
end
|
||||
subgraph API["API Layer (Thin Client)"]
|
||||
RepositoryClient["RepositoryClient<br/>(Handle-based)"]
|
||||
JellyfinClient["JellyfinClient<br/>(Helper)"]
|
||||
end
|
||||
end
|
||||
|
||||
Frontend -->|"Tauri IPC (invoke)"| Backend
|
||||
|
||||
subgraph Backend["Rust Backend (Business Logic)"]
|
||||
subgraph Commands["Tauri Commands (90+)"]
|
||||
PlayerCmds["player.rs"]
|
||||
RepoCmds["repository.rs (27)"]
|
||||
PlaybackModeCmds["playback_mode.rs (5)"]
|
||||
StorageCmds["storage.rs"]
|
||||
ConnectivityCmds["connectivity.rs (7)"]
|
||||
end
|
||||
|
||||
subgraph Core["Core Modules"]
|
||||
MediaSessionManager["MediaSessionManager<br/>(Audio/Movie/TvShow/Idle)"]
|
||||
|
||||
PlayerController["PlayerController<br/>+ PlayerBackend<br/>+ QueueManager"]
|
||||
|
||||
Repository["Repository Layer<br/>HybridRepository (cache-first)<br/>OnlineRepository (HTTP)<br/>OfflineRepository (SQLite)"]
|
||||
|
||||
PlaybackModeManager["PlaybackModeManager<br/>(Local/Remote/Idle)"]
|
||||
|
||||
ConnectivityMonitor["ConnectivityMonitor<br/>(Adaptive polling)"]
|
||||
|
||||
HttpClient["HttpClient<br/>(Exponential backoff retry)"]
|
||||
end
|
||||
|
||||
subgraph Storage["Storage Layer"]
|
||||
DatabaseService["DatabaseService<br/>(Async trait)"]
|
||||
SQLite["SQLite Database<br/>(13 tables)"]
|
||||
end
|
||||
|
||||
Commands --> Core
|
||||
Core --> Storage
|
||||
Repository --> HttpClient
|
||||
Repository --> DatabaseService
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Detailed Documentation
|
||||
|
||||
Each major subsystem is documented in its own file in this directory:
|
||||
|
||||
| Document | Contents |
|
||||
|----------|----------|
|
||||
| [01 - Rust Backend](01-rust-backend.md) | Media session state machine, player state machine, playback mode, media items, queue manager, favorites, player backend trait, player controller, playlist system, Tauri commands |
|
||||
| [02 - Svelte Frontend](02-svelte-frontend.md) | Store structure, music library navigation, playback reporting, repository architecture, playback mode system, database service abstraction, component hierarchy, MiniPlayer, sleep timer, auto-play, navigation guard, playlist management UI |
|
||||
| [03 - Data Flow](03-data-flow.md) | Repository query flow (cache-first), playback initiation, playback mode transfer, queue navigation, volume control |
|
||||
| [04 - Type Sync & Threading](04-type-sync-and-threading.md) | Rust/TypeScript type synchronization, Tauri v2 IPC parameter naming convention, thread safety patterns |
|
||||
| [05 - Platform Backends](05-platform-backends.md) | Player events system, MpvBackend (Linux), ExoPlayerBackend (Android), MediaSession & remote volume, album art caching, backend initialization |
|
||||
| [06 - Downloads & Offline](06-downloads-and-offline.md) | Download manager, download worker, smart caching engine, download/offline commands, player integration, frontend store, UI components |
|
||||
| [07 - Connectivity](07-connectivity.md) | HTTP client with retry logic, connectivity monitor, network resilience architecture |
|
||||
| [08 - Database Design](08-database-design.md) | Entity relationships, all table definitions (servers, users, libraries, items, user_data, downloads, media_streams, sync_queue, thumbnails, playlists), key queries, data flow diagrams, storage estimates |
|
||||
| [09 - Security](09-security.md) | Authentication token storage, secure storage module, network security, local data protection |
|
||||
|
||||
---
|
||||
|
||||
## File Structure Summary
|
||||
|
||||
```
|
||||
src-tauri/src/
|
||||
├── lib.rs # Tauri app setup, state initialization
|
||||
├── commands/ # Tauri command handlers (90+ commands)
|
||||
│ ├── mod.rs # Command exports
|
||||
│ ├── player.rs # 16 player commands
|
||||
│ ├── repository.rs # 27 repository commands
|
||||
│ ├── playlist.rs # 7 playlist commands
|
||||
│ ├── playback_mode.rs # 5 playback mode commands
|
||||
│ ├── connectivity.rs # 7 connectivity commands
|
||||
│ ├── storage.rs # Storage & database commands
|
||||
│ ├── download.rs # 7 download commands
|
||||
│ ├── offline.rs # 3 offline commands
|
||||
│ └── sync.rs # Sync queue commands
|
||||
├── repository/ # Repository pattern implementation
|
||||
│ ├── mod.rs # MediaRepository trait, handle management
|
||||
│ ├── types.rs # RepoError, Library, MediaItem, etc.
|
||||
│ ├── hybrid.rs # HybridRepository with cache-first racing
|
||||
│ ├── online.rs # OnlineRepository (HTTP API)
|
||||
│ └── offline.rs # OfflineRepository (SQLite queries)
|
||||
├── playback_mode/ # Playback mode manager
|
||||
│ └── mod.rs # PlaybackMode enum, transfer logic
|
||||
├── connectivity/ # Connectivity monitoring
|
||||
│ └── mod.rs # ConnectivityMonitor, adaptive polling
|
||||
├── jellyfin/ # Jellyfin API client
|
||||
│ ├── mod.rs # Module exports
|
||||
│ ├── http_client.rs # HTTP client with retry logic
|
||||
│ └── client.rs # JellyfinClient for API calls
|
||||
├── storage/ # Database layer
|
||||
│ ├── mod.rs # Database struct, migrations
|
||||
│ ├── db_service.rs # DatabaseService trait (async wrapper)
|
||||
│ ├── schema.rs # Table definitions
|
||||
│ └── queries/ # Query modules
|
||||
├── download/ # Download manager module
|
||||
│ ├── mod.rs # DownloadManager, DownloadInfo, DownloadTask
|
||||
│ ├── worker.rs # DownloadWorker, HTTP streaming, retry logic
|
||||
│ ├── events.rs # DownloadEvent enum
|
||||
│ └── cache.rs # SmartCache, CacheConfig, LRU eviction
|
||||
└── player/ # Player subsystem
|
||||
├── mod.rs # PlayerController
|
||||
├── session.rs # MediaSessionManager, MediaSessionType
|
||||
├── state.rs # PlayerState, PlayerEvent
|
||||
├── media.rs # MediaItem, MediaSource, MediaType
|
||||
├── queue.rs # QueueManager, RepeatMode
|
||||
├── backend.rs # PlayerBackend trait, NullBackend
|
||||
├── events.rs # PlayerStatusEvent, TauriEventEmitter
|
||||
├── mpv/ # Linux MPV backend
|
||||
│ ├── mod.rs # MpvBackend implementation
|
||||
│ └── event_loop.rs # Dedicated thread for MPV operations
|
||||
└── android/ # Android ExoPlayer backend
|
||||
└── mod.rs # ExoPlayerBackend + JNI bindings
|
||||
|
||||
src/lib/
|
||||
├── api/ # Thin API layer (~200 lines total)
|
||||
│ ├── types.ts # TypeScript type definitions
|
||||
│ ├── repository-client.ts # RepositoryClient wrapper (~100 lines)
|
||||
│ ├── client.ts # JellyfinClient (helper for streaming)
|
||||
│ └── sessions.ts # SessionsApi (remote session control)
|
||||
├── services/
|
||||
│ ├── playerEvents.ts # Tauri event listener for player events
|
||||
│ └── playbackReporting.ts # Thin wrapper (~50 lines)
|
||||
├── stores/ # Thin reactive wrappers over Rust commands
|
||||
│ ├── index.ts # Re-exports
|
||||
│ ├── auth.ts # Auth store (calls Rust commands)
|
||||
│ ├── player.ts # Player store
|
||||
│ ├── queue.ts # Queue store
|
||||
│ ├── library.ts # Library store
|
||||
│ ├── playbackMode.ts # Playback mode store (~150 lines)
|
||||
│ ├── connectivity.ts # Connectivity store (~250 lines)
|
||||
│ └── downloads.ts # Downloads store with event listeners
|
||||
└── components/
|
||||
├── Search.svelte
|
||||
├── player/ # Player UI components
|
||||
├── playlist/ # Playlist modals (Create, AddTo)
|
||||
├── sessions/ # Remote session control UI
|
||||
├── downloads/ # Download UI components
|
||||
└── library/ # Library UI components + PlaylistDetailView
|
||||
```
|
||||
|
||||
## Key Architecture Changes
|
||||
|
||||
**What moved to Rust (~3,500 lines of business logic):**
|
||||
1. **HTTP Client** (338 lines) - Retry logic with exponential backoff
|
||||
2. **Connectivity Monitor** (301 lines) - Adaptive polling, event emission
|
||||
3. **Repository Pattern** (1061 lines) - Cache-first hybrid with parallel racing
|
||||
4. **Database Service** - Async wrapper preventing UI freezing
|
||||
5. **Playback Mode** (303 lines) - Local/remote transfer coordination
|
||||
|
||||
**Svelte/TypeScript frontend (~20.5k non-test lines, plus ~9.6k test lines):**
|
||||
- Components + routes (~14.6k lines) — UI and presentation
|
||||
- Stores (~3.4k lines) — reactive state that invokes Rust commands and listens for events
|
||||
- api / services / utils (~2.4k lines) — typed clients, event listeners, conversion helpers
|
||||
|
||||
The frontend is genuinely UI-heavy; business decisions live in Rust, but the UI owns layout, navigation, and interaction state.
|
||||
|
||||
**Total Commands:** 90+ Tauri commands across 14 command modules
|
||||
Vendored
+156
@@ -0,0 +1,156 @@
|
||||
# Building and Pushing the JellyTau Builder Image
|
||||
|
||||
This document explains how to create and push the pre-built builder Docker image to your registry for use in Gitea Act CI/CD.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker installed and running
|
||||
- Access to your Docker registry (e.g., `gitea.tourolle.paris`)
|
||||
- Docker registry credentials configured (`docker login`)
|
||||
|
||||
## Building the Builder Image
|
||||
|
||||
### Step 1: Build the Image Locally
|
||||
|
||||
```bash
|
||||
# From the project root
|
||||
docker build -f Dockerfile.builder -t jellytau-builder:latest .
|
||||
```
|
||||
|
||||
This creates a local image with:
|
||||
- All system dependencies
|
||||
- Rust with Android targets
|
||||
- Android SDK and NDK
|
||||
- Node.js and Bun
|
||||
- All build tools pre-installed
|
||||
|
||||
### Step 2: Tag for Your Registry
|
||||
|
||||
Replace `gitea.tourolle.paris/dtourolle` with your actual registry path:
|
||||
|
||||
```bash
|
||||
docker tag jellytau-builder:latest gitea.tourolle.paris/dtourolle/jellytau-builder:latest
|
||||
```
|
||||
|
||||
### Step 3: Login to Your Registry
|
||||
|
||||
If not already logged in:
|
||||
|
||||
```bash
|
||||
docker login gitea.tourolle.paris
|
||||
```
|
||||
|
||||
### Step 4: Push to Registry
|
||||
|
||||
```bash
|
||||
docker push gitea.tourolle.paris/dtourolle/jellytau-builder:latest
|
||||
```
|
||||
|
||||
## Complete One-Liner
|
||||
|
||||
```bash
|
||||
docker build -f Dockerfile.builder -t jellytau-builder:latest . && \
|
||||
docker tag jellytau-builder:latest gitea.tourolle.paris/dtourolle/jellytau-builder:latest && \
|
||||
docker push gitea.tourolle.paris/dtourolle/jellytau-builder:latest
|
||||
```
|
||||
|
||||
## Verifying the Build
|
||||
|
||||
Check that the image was pushed successfully:
|
||||
|
||||
```bash
|
||||
# List images in your registry (depends on registry API support)
|
||||
docker search gitea.tourolle.paris/dtourolle/jellytau-builder
|
||||
|
||||
# Or pull and test locally
|
||||
docker pull gitea.tourolle.paris/dtourolle/jellytau-builder:latest
|
||||
docker run -it gitea.tourolle.paris/dtourolle/jellytau-builder:latest bun --version
|
||||
```
|
||||
|
||||
## Using in CI/CD
|
||||
|
||||
The workflow at `.gitea/workflows/build-and-test.yml` automatically uses:
|
||||
```yaml
|
||||
container:
|
||||
image: gitea.tourolle.paris/dtourolle/jellytau-builder:latest
|
||||
```
|
||||
|
||||
Once pushed, your CI/CD pipeline will use this pre-built image instead of installing everything during the build, saving significant time.
|
||||
|
||||
## Updating the Builder Image
|
||||
|
||||
When dependencies change (new Rust version, Android SDK update, etc.):
|
||||
|
||||
1. Update `Dockerfile.builder` with the new configuration
|
||||
2. Rebuild and push with a new tag:
|
||||
|
||||
```bash
|
||||
docker build -f Dockerfile.builder -t jellytau-builder:v1.2.0 .
|
||||
docker tag jellytau-builder:v1.2.0 gitea.tourolle.paris/dtourolle/jellytau-builder:v1.2.0
|
||||
docker push gitea.tourolle.paris/dtourolle/jellytau-builder:v1.2.0
|
||||
```
|
||||
|
||||
3. Update the workflow to use the new tag:
|
||||
|
||||
```yaml
|
||||
container:
|
||||
image: gitea.tourolle.paris/dtourolle/jellytau-builder:v1.2.0
|
||||
```
|
||||
|
||||
## Image Contents
|
||||
|
||||
The builder image includes:
|
||||
|
||||
- **Base OS**: Ubuntu 24.04
|
||||
- **Languages**:
|
||||
- Rust (stable) with targets: aarch64-linux-android, armv7-linux-androideabi, x86_64-linux-android
|
||||
- Node.js 20.x
|
||||
- OpenJDK 17 (for Android)
|
||||
- **Tools**:
|
||||
- Bun package manager
|
||||
- Android SDK 34
|
||||
- Android NDK 27.0.11902837
|
||||
- Build essentials (gcc, make, etc.)
|
||||
- Git, curl, wget
|
||||
- libssl, libclang development libraries
|
||||
- **Pre-configured**:
|
||||
- Rust toolchain components (rustfmt, clippy)
|
||||
- Android SDK/NDK environment variables
|
||||
- All paths optimized for building
|
||||
|
||||
## Build Time
|
||||
|
||||
First build takes ~15-20 minutes depending on internet speed (downloads Android SDK/NDK).
|
||||
Subsequent builds are cached and take seconds.
|
||||
|
||||
## Storage
|
||||
|
||||
The built image is approximately **4-5 GB**. Ensure your registry has sufficient storage.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Image not found" in CI
|
||||
- Verify the image name matches exactly in the workflow
|
||||
- Check that the image was successfully pushed: `docker push` output should show successful layers
|
||||
- Ensure Gitea has access to your registry (check network/firewall)
|
||||
|
||||
### Build fails with "command not found"
|
||||
- The image may not have finished pushing. Wait a few moments and retry the CI job.
|
||||
- Check that all layers were pushed successfully in the push output.
|
||||
|
||||
### Registry authentication in CI
|
||||
If your registry requires credentials in CI:
|
||||
1. Create a deploy token in your registry
|
||||
2. Add to Gitea secrets as `REGISTRY_USERNAME` and `REGISTRY_TOKEN`
|
||||
3. Use in workflow:
|
||||
```yaml
|
||||
- name: Login to Registry
|
||||
run: |
|
||||
docker login gitea.tourolle.paris -u ${{ secrets.REGISTRY_USERNAME }} -p ${{ secrets.REGISTRY_TOKEN }}
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Docker Build Documentation](https://docs.docker.com/build/)
|
||||
- [Docker Push Documentation](https://docs.docker.com/engine/reference/commandline/push/)
|
||||
- [Dockerfile Reference](https://docs.docker.com/engine/reference/builder/)
|
||||
Vendored
+282
@@ -0,0 +1,282 @@
|
||||
# Docker & CI/CD Setup for JellyTau
|
||||
|
||||
This document explains how to use the Docker configuration and Gitea Act CI/CD pipeline for building and testing JellyTau.
|
||||
|
||||
## Overview
|
||||
|
||||
The setup includes:
|
||||
- **Dockerfile.builder**: Pre-built image with all dependencies (push to your registry)
|
||||
- **Dockerfile**: Multi-stage build for local testing and building
|
||||
- **docker-compose.yml**: Orchestration for local development and testing
|
||||
- **.gitea/workflows/build-and-test.yml**: Automated CI/CD pipeline using pre-built builder image
|
||||
|
||||
### Quick Start
|
||||
|
||||
**For CI/CD (Gitea Actions)**:
|
||||
1. Build and push builder image (see [build-builder-image.md](build-builder-image.md))
|
||||
2. Push to master branch - workflow runs automatically
|
||||
3. Check Actions tab for results and APK artifacts
|
||||
|
||||
**For Local Testing**:
|
||||
```bash
|
||||
docker-compose run test # Run tests
|
||||
docker-compose run android-build # Build APK
|
||||
docker-compose run dev # Interactive shell
|
||||
```
|
||||
|
||||
## Docker Usage
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker Engine 20.10+
|
||||
- Docker Compose 2.0+ (if using docker-compose)
|
||||
- At least 10GB free disk space (for Android SDK and build artifacts)
|
||||
|
||||
### Building the Docker Image
|
||||
|
||||
```bash
|
||||
# Build the complete image
|
||||
docker build -t jellytau:latest .
|
||||
|
||||
# Build specific target
|
||||
docker build -t jellytau:test --target test .
|
||||
docker build -t jellytau:android --target android-build .
|
||||
```
|
||||
|
||||
### Using Docker Compose
|
||||
|
||||
#### Run Tests Only
|
||||
```bash
|
||||
docker-compose run test
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Install all dependencies
|
||||
2. Run frontend tests (Vitest)
|
||||
3. Run Rust backend tests
|
||||
4. Report results
|
||||
|
||||
#### Build Android APK
|
||||
```bash
|
||||
docker-compose run android-build
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Run tests first (depends on test service)
|
||||
2. If tests pass, build the Android APK
|
||||
3. Output APK files to `src-tauri/gen/android/app/build/outputs/apk/`
|
||||
|
||||
#### Interactive Development
|
||||
```bash
|
||||
docker-compose run dev
|
||||
```
|
||||
|
||||
This starts an interactive shell with all development tools available. From here you can:
|
||||
```bash
|
||||
bun install
|
||||
bun run build
|
||||
bun test
|
||||
bun run tauri android build --apk true
|
||||
```
|
||||
|
||||
#### Run All Services in Sequence
|
||||
```bash
|
||||
docker-compose up --abort-on-container-exit
|
||||
```
|
||||
|
||||
### Extracting Build Artifacts
|
||||
|
||||
After a successful build, APK files are located in:
|
||||
```
|
||||
src-tauri/gen/android/app/build/outputs/apk/
|
||||
```
|
||||
|
||||
Copy to your host machine:
|
||||
```bash
|
||||
docker cp jellytau-android-build:/app/src-tauri/gen/android/app/build/outputs/apk ./apk-output
|
||||
```
|
||||
|
||||
## Gitea Act CI/CD Pipeline
|
||||
|
||||
The `.gitea/workflows/build-and-test.yml` workflow automates:
|
||||
|
||||
**Single Job**: Runs on every push to `master` and PRs
|
||||
- Uses pre-built builder image (no setup time)
|
||||
- Installs project dependencies
|
||||
- Runs frontend tests (Vitest)
|
||||
- Runs Rust backend tests
|
||||
- Builds the frontend
|
||||
- Builds the Android APK
|
||||
- Uploads APK as artifact (30-day retention)
|
||||
|
||||
The workflow skips markdown files to avoid unnecessary builds.
|
||||
|
||||
### Workflow Triggers
|
||||
|
||||
The workflow runs on:
|
||||
- Push to `master` or `main` branches
|
||||
- Pull requests to `master` or `main` branches
|
||||
- Can be extended with: `workflow_dispatch` for manual triggers
|
||||
|
||||
### Setting Up the Builder Image
|
||||
|
||||
Before using the CI/CD pipeline, you must build and push the builder image:
|
||||
|
||||
```bash
|
||||
# Build the image
|
||||
docker build -f Dockerfile.builder -t jellytau-builder:latest .
|
||||
|
||||
# Tag for your registry
|
||||
docker tag jellytau-builder:latest gitea.tourolle.paris/dtourolle/jellytau-builder:latest
|
||||
|
||||
# Push to registry
|
||||
docker push gitea.tourolle.paris/dtourolle/jellytau-builder:latest
|
||||
```
|
||||
|
||||
See [build-builder-image.md](build-builder-image.md) for detailed instructions.
|
||||
|
||||
### Setting Up Gitea Act
|
||||
|
||||
1. **Ensure builder image is pushed** (see above)
|
||||
|
||||
2. **Push to Gitea repository**:
|
||||
The workflow will automatically trigger on push to `master` or pull requests
|
||||
|
||||
3. **View workflow runs in Gitea UI**:
|
||||
- Navigate to your repository
|
||||
- Go to Actions tab
|
||||
- Click on workflow runs to see logs
|
||||
|
||||
4. **Test locally** (optional):
|
||||
```bash
|
||||
# Install act if needed
|
||||
curl https://gitea.com/actions/setup-act/releases/download/v0.25.0/act-0.25.0-linux-x86_64.tar.gz | tar xz
|
||||
|
||||
# Run locally (requires builder image to be available)
|
||||
./act push --file .gitea/workflows/build-and-test.yml
|
||||
```
|
||||
|
||||
### Customizing the Workflow
|
||||
|
||||
#### Modify Build Triggers
|
||||
Edit `.gitea/workflows/build-and-test.yml` to change when builds run:
|
||||
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop # Add more branches
|
||||
paths:
|
||||
- 'src/**' # Only run if src/ changes
|
||||
- 'src-tauri/**' # Only run if Rust code changes
|
||||
```
|
||||
|
||||
#### Add Notifications
|
||||
Add Slack, Discord, or email notifications on build completion:
|
||||
|
||||
```yaml
|
||||
- name: Notify on success
|
||||
if: success()
|
||||
run: |
|
||||
curl -X POST https://slack-webhook-url...
|
||||
```
|
||||
|
||||
#### Customize APK Upload
|
||||
Modify artifact retention or add to cloud storage:
|
||||
|
||||
```yaml
|
||||
- name: Upload APK to S3
|
||||
uses: actions/s3-sync@v1
|
||||
with:
|
||||
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY }}
|
||||
aws_secret_access_key: ${{ secrets.AWS_SECRET_KEY }}
|
||||
aws_bucket: my-apk-bucket
|
||||
source_dir: src-tauri/gen/android/app/build/outputs/apk/
|
||||
```
|
||||
|
||||
## Environment Setup in CI
|
||||
|
||||
### Secret Variables
|
||||
To use secrets in the workflow, set them in Gitea:
|
||||
|
||||
1. Go to Repository Settings → Secrets
|
||||
2. Add secrets like:
|
||||
- `AWS_ACCESS_KEY` for S3 uploads
|
||||
- `SLACK_WEBHOOK_URL` for notifications
|
||||
- `GITHUB_TOKEN` for releases (pre-configured)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Out of Memory During Build
|
||||
Android builds are memory-intensive. If you get OOM errors:
|
||||
|
||||
```bash
|
||||
# Limit memory in docker-compose
|
||||
services:
|
||||
android-build:
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 6G
|
||||
```
|
||||
|
||||
Or increase Docker's memory allocation in Docker Desktop settings.
|
||||
|
||||
### Android SDK Download Timeout
|
||||
If downloads timeout, increase timeout or download manually:
|
||||
|
||||
```bash
|
||||
# In container, with longer timeout
|
||||
timeout 600 sdkmanager --sdk_root=$ANDROID_HOME ...
|
||||
```
|
||||
|
||||
### Rust Compilation Errors
|
||||
Make sure Rust is updated:
|
||||
|
||||
```bash
|
||||
rustup update
|
||||
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
|
||||
```
|
||||
|
||||
### Cache Issues
|
||||
Clear Docker cache and rebuild:
|
||||
|
||||
```bash
|
||||
docker-compose down -v # Remove volumes
|
||||
docker system prune # Clean up dangling images
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Cache Reuse**: Both Docker and Gitea Act cache dependencies across runs
|
||||
2. **Parallel Steps**: The workflow runs frontend and Rust tests in series; consider parallelizing for faster CI
|
||||
3. **Incremental Builds**: Rust and Node caches persist between runs
|
||||
4. **Docker Buildkit**: Enable for faster builds:
|
||||
```bash
|
||||
DOCKER_BUILDKIT=1 docker build .
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Dockerfile uses `ubuntu:24.04` base image from official Docker Hub
|
||||
- NDK is downloaded from official Google servers (verified via HTTPS)
|
||||
- No credentials are stored in the Dockerfile
|
||||
- Use Gitea Secrets for sensitive values (API keys, tokens, etc.)
|
||||
- Lock dependency versions in `Cargo.toml` and `package.json`
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Test locally with `docker-compose up`
|
||||
2. Push to your Gitea repository
|
||||
3. Monitor workflow runs in the Actions tab
|
||||
4. Configure secrets in repository settings for production builds
|
||||
5. Set up artifact retention policies (currently 30 days)
|
||||
|
||||
## References
|
||||
|
||||
- [Gitea Actions Documentation](https://docs.gitea.io/en-us/actions/)
|
||||
- [Docker Multi-stage Builds](https://docs.docker.com/build/building/multi-stage/)
|
||||
- [Android Build Tools](https://developer.android.com/studio/command-line)
|
||||
- [Tauri Android Guide](https://tauri.app/v1/guides/building/android)
|
||||
@@ -0,0 +1,300 @@
|
||||
# Release Checklist
|
||||
|
||||
Quick reference for creating a JellyTau release.
|
||||
|
||||
## Pre-Release (1-2 days before)
|
||||
|
||||
- [ ] Code is on `master`/`main` branch
|
||||
- [ ] All feature branches are merged and tested
|
||||
- [ ] No failing tests locally: `bun run test` and `bun run test:rust`
|
||||
- [ ] Requirement traceability check passes: `bun run traces:json`
|
||||
- [ ] Type checking passes: `bun run check`
|
||||
|
||||
## Update Version (Day before)
|
||||
|
||||
- [ ] Decide on version number (semantic versioning)
|
||||
- Example: `v1.2.0` (major.minor.patch)
|
||||
- Example: `v1.0.0-rc1` (release candidate)
|
||||
- Example: `v1.0.0-beta` (beta)
|
||||
|
||||
- [ ] Update version in files:
|
||||
```bash
|
||||
# Check these files for version numbers
|
||||
cat package.json | grep version
|
||||
cat src-tauri/tauri.conf.json | grep version
|
||||
cat src-tauri/Cargo.toml | grep version
|
||||
```
|
||||
|
||||
- [ ] Update `CHANGELOG.md`:
|
||||
- [ ] Add section for new version
|
||||
- [ ] List all features added
|
||||
- [ ] List all bugs fixed
|
||||
- [ ] List breaking changes (if any)
|
||||
- [ ] Add upgrade instructions (if needed)
|
||||
- [ ] Format: Markdown with clear sections
|
||||
|
||||
- [ ] Update `README.md`:
|
||||
- [ ] Update any version references
|
||||
- [ ] Update feature list if applicable
|
||||
- [ ] Update requirements if changed
|
||||
|
||||
- [ ] Commit changes:
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Bump version to v1.2.0"
|
||||
git push origin master
|
||||
```
|
||||
|
||||
## Final Check Before Release
|
||||
|
||||
- [ ] Run full test suite:
|
||||
```bash
|
||||
bun run test # Frontend tests
|
||||
bun run test:rust # Rust tests
|
||||
bun run check # Type checking
|
||||
```
|
||||
|
||||
- [ ] Build locally (optional but recommended):
|
||||
```bash
|
||||
# Test Linux build
|
||||
bun run tauri build
|
||||
|
||||
# Test Android build
|
||||
bun run tauri android build
|
||||
```
|
||||
|
||||
- [ ] No uncommitted changes:
|
||||
```bash
|
||||
git status # Should show clean working directory
|
||||
```
|
||||
|
||||
## Release (Tag & Push)
|
||||
|
||||
```bash
|
||||
# 1. Create annotated tag with release notes
|
||||
git tag -a v1.2.0 -m "Release version 1.2.0
|
||||
|
||||
Features:
|
||||
- New feature 1
|
||||
- New feature 2
|
||||
|
||||
Fixes:
|
||||
- Fixed bug 1
|
||||
- Fixed bug 2
|
||||
|
||||
Improvements:
|
||||
- Performance improvement 1
|
||||
- UI improvement 1
|
||||
|
||||
Breaking Changes:
|
||||
- None (or list if applicable)
|
||||
|
||||
Migration:
|
||||
- No action required (or include steps if applicable)"
|
||||
|
||||
# 2. Push tag to trigger workflow
|
||||
git push origin v1.2.0
|
||||
|
||||
# 3. Monitor in Gitea Actions
|
||||
# Go to Actions tab and watch the workflow run
|
||||
```
|
||||
|
||||
## During Release (While Workflow Runs)
|
||||
|
||||
- [ ] Watch workflow progress in Gitea Actions
|
||||
- [ ] Monitor for test failures
|
||||
- [ ] Monitor for build failures
|
||||
- [ ] Check build logs if any step fails
|
||||
|
||||
## After Release (Workflow Complete)
|
||||
|
||||
- [ ] Download artifacts from release page:
|
||||
- [ ] `jellytau_*.AppImage` (Linux)
|
||||
- [ ] `jellytau_*.deb` (Linux)
|
||||
- [ ] `jellytau-release.apk` (Android)
|
||||
- [ ] `jellytau-release.aab` (Android)
|
||||
|
||||
- [ ] Basic testing of artifacts:
|
||||
- [ ] Linux AppImage runs
|
||||
- [ ] Linux DEB installs and runs
|
||||
- [ ] Android APK installs (via `adb` or sideload)
|
||||
|
||||
- [ ] Verify release page:
|
||||
- [ ] Title is correct: "JellyTau vX.Y.Z"
|
||||
- [ ] Release notes are formatted correctly
|
||||
- [ ] All artifacts are uploaded
|
||||
- [ ] Release type is correct (prerelease vs release)
|
||||
|
||||
- [ ] Announce release:
|
||||
- [ ] Post to relevant channels/communities
|
||||
- [ ] Update website/docs
|
||||
- [ ] Tag contributors if applicable
|
||||
|
||||
## Rollback (If Issues Found)
|
||||
|
||||
If critical issues are found after release:
|
||||
|
||||
```bash
|
||||
# Option 1: Delete tag locally and remotely
|
||||
git tag -d v1.2.0
|
||||
git push origin :refs/tags/v1.2.0
|
||||
|
||||
# Option 2: Mark as prerelease in release page
|
||||
# Then plan immediate patch release (v1.2.1)
|
||||
|
||||
# Option 3: Create hotfix branch and release v1.2.1
|
||||
git checkout -b hotfix/v1.2.1
|
||||
# Fix issues
|
||||
git commit -m "Fix critical issue"
|
||||
git tag v1.2.1
|
||||
git push origin hotfix/v1.2.1 v1.2.1
|
||||
```
|
||||
|
||||
## Version Examples
|
||||
|
||||
### Major Release
|
||||
```
|
||||
v2.0.0 - Major version bump
|
||||
- Significant new features
|
||||
- Breaking API changes
|
||||
- Major UI redesign
|
||||
```
|
||||
|
||||
### Minor Release
|
||||
```
|
||||
v1.2.0 - Feature release
|
||||
- New features
|
||||
- Backward compatible
|
||||
- Bug fixes
|
||||
```
|
||||
|
||||
### Patch Release
|
||||
```
|
||||
v1.1.1 - Bug fix/patch
|
||||
- Bug fixes only
|
||||
- No new features
|
||||
- Backward compatible
|
||||
```
|
||||
|
||||
### Pre-releases
|
||||
```
|
||||
v1.2.0-alpha - Early development
|
||||
v1.2.0-beta - Late development, feature complete
|
||||
v1.2.0-rc1 - Release candidate, minimal fixes only
|
||||
```
|
||||
|
||||
## File Locations
|
||||
|
||||
Key files for versioning:
|
||||
- `package.json` - Frontend version
|
||||
- `src-tauri/tauri.conf.json` - Tauri config version
|
||||
- `src-tauri/Cargo.toml` - Rust version
|
||||
- `CHANGELOG.md` - Release history
|
||||
- `README.md` - Project documentation
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Tests Fail Before Release
|
||||
1. Don't push tag yet
|
||||
2. Fix failing tests locally
|
||||
3. Push fixes to master
|
||||
4. Re-run test suite
|
||||
5. Then tag and push
|
||||
|
||||
### Build Fails in CI
|
||||
1. Check detailed logs in Gitea Actions
|
||||
2. Fix issue locally
|
||||
3. Delete tag: `git tag -d v1.2.0 && git push origin :refs/tags/v1.2.0`
|
||||
4. Push fix to master
|
||||
5. Create new tag with fix
|
||||
|
||||
### Release Already Exists
|
||||
1. If workflow runs twice, artifacts may conflict
|
||||
2. Check release page
|
||||
3. If duplicates exist, delete and re-release
|
||||
|
||||
### Artifacts Missing
|
||||
1. Check build logs for errors
|
||||
2. Verify platform-specific dependencies
|
||||
3. Delete tag and retry after fixes
|
||||
|
||||
## Performance Tips
|
||||
|
||||
- Tests: ~5-10 minutes
|
||||
- Linux build: ~10-15 minutes
|
||||
- Android build: ~15-20 minutes
|
||||
- Total release time: ~30-45 minutes
|
||||
|
||||
First build takes longer (cache warming). Subsequent releases are faster due to caching.
|
||||
|
||||
## Template: Release Notes
|
||||
|
||||
```
|
||||
## 🎉 JellyTau vX.Y.Z
|
||||
|
||||
### ✨ Features
|
||||
- New feature 1
|
||||
- New feature 2
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- Fixed issue #123
|
||||
- Fixed issue #456
|
||||
|
||||
### 🚀 Performance
|
||||
- Improvement 1
|
||||
- Improvement 2
|
||||
|
||||
### 📱 Downloads
|
||||
- [Linux AppImage](#) - Run on any Linux
|
||||
- [Linux DEB](#) - Install on Ubuntu/Debian
|
||||
- [Android APK](#) - Install on Android devices
|
||||
- [Android AAB](#) - For Google Play Store
|
||||
|
||||
### 📋 Requirements
|
||||
**Linux:** 64-bit, GLIBC 2.29+
|
||||
**Android:** 8.0+
|
||||
|
||||
### 🔗 Links
|
||||
- [Changelog](../../CHANGELOG.md)
|
||||
- [Issues](../../issues)
|
||||
- [Discussion](../../discussions)
|
||||
|
||||
---
|
||||
Built with Tauri, SvelteKit, and Rust 🦀
|
||||
```
|
||||
|
||||
## Quick Commands
|
||||
|
||||
```bash
|
||||
# View existing tags
|
||||
git tag -l
|
||||
|
||||
# Create release locally (dry run)
|
||||
git tag -a v1.2.0 -m "Release v1.2.0" --dry-run
|
||||
|
||||
# List commits since last tag
|
||||
git log v1.1.0..HEAD --oneline
|
||||
|
||||
# Show tag details
|
||||
git show v1.2.0
|
||||
|
||||
# Rename tag (if needed)
|
||||
git tag v1.2.0_old v1.2.0
|
||||
git tag -d v1.2.0
|
||||
git push origin v1.2.0_old v1.2.0
|
||||
|
||||
# Delete tag locally and remotely
|
||||
git tag -d v1.2.0
|
||||
git push origin :refs/tags/v1.2.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Tips:**
|
||||
- ✅ Always test locally before release
|
||||
- ✅ Use semantic versioning consistently
|
||||
- ✅ Document changes in CHANGELOG
|
||||
- ✅ Wait for full workflow completion
|
||||
- ✅ Test release artifacts before announcing
|
||||
|
||||
**Remember:** A good release is a tested release! 🚀
|
||||
@@ -0,0 +1,454 @@
|
||||
# Requirements Specification
|
||||
|
||||
This document captures JellyTau's user requirements, software requirements,
|
||||
traceability matrix, test traceability, and known technical debt.
|
||||
|
||||
For a narrative overview of the system design, see
|
||||
[docs/architecture/](architecture/). For development workflows, see the
|
||||
[README](../README.md) and [scripts/README.md](../scripts/README.md).
|
||||
|
||||
## 1. User Requirements
|
||||
|
||||
| ID | Requirement | Priority | Status |
|
||||
|----|-------------|----------|--------|
|
||||
| UR-001 | Run the app on multiple platforms (Linux, Android) | High | In Progress |
|
||||
| UR-002 | Access media when online or offline | High | Done |
|
||||
| UR-003 | Play videos | High | Done |
|
||||
| UR-004 | Play audio uninterrupted | High | Done |
|
||||
| UR-005 | Control media playback (pause, play, skip, scrub) | High | Done |
|
||||
| UR-006 | Control media when device is on lock screen or via BLE headsets | Medium | Done |
|
||||
| UR-007 | Navigate media in library | High | Done |
|
||||
| UR-008 | Search media across libraries | High | Done |
|
||||
| UR-009 | Connect to Jellyfin to access media | High | Done |
|
||||
| UR-010 | Control playback of Jellyfin remote sessions | Low | Done |
|
||||
| UR-011 | Download media on demand | Medium | Done |
|
||||
| UR-012 | Login info shall be stored securely and persistently | High | Done |
|
||||
| UR-013 | View and manage downloaded media | Medium | Done |
|
||||
| UR-014 | Make and edit playlists of music that sync back to Jellyfin | Medium | Done |
|
||||
| UR-015 | View and manage current audio queue (add, reorder tracks) | Medium | Done |
|
||||
| UR-016 | Change system settings while playing (brightness, volume) | Low | Planned |
|
||||
| UR-017 | Like or unlike audio, albums, movies, etc. | Medium | Done |
|
||||
| UR-018 | Choose to download series, albums, songs, artist discography | Medium | Done |
|
||||
| UR-019 | Resume playback from where you left off (movies, shows, albums) | High | Done |
|
||||
| UR-020 | Select subtitles for video content | High | Done |
|
||||
| UR-021 | Select audio track for video content | High | Done |
|
||||
| UR-022 | Control streaming quality and transcoding settings | Medium | Planned |
|
||||
| UR-023 | View "Next Up" / Continue Watching on home screen; auto-play next episode with countdown popup and configurable episode limit | Medium | Done |
|
||||
| UR-024 | View recently added content on server | Medium | Done |
|
||||
| UR-025 | Sync watch history and progress back to Jellyfin | High | Done |
|
||||
| UR-026 | Sleep timer for audio and video playback (roller UI, time/track/episode modes) | Low | Done |
|
||||
| UR-027 | Audio equalizer for sound customization | Low | Planned |
|
||||
| UR-028 | Navigate to artist/album by tapping names in now playing view | High | Done |
|
||||
| UR-029 | Toggle between grid and list view in library | Medium | Done |
|
||||
| UR-030 | Quick genre browsing and filtering | Medium | Done |
|
||||
| UR-031 | Crossfade between audio tracks | Low | Done (Linux only) |
|
||||
| UR-032 | Gapless playback for seamless album listening | Medium | Done (Linux only) |
|
||||
| UR-033 | Volume normalization to prevent volume jumps between tracks | Low | Done (Linux only) |
|
||||
| UR-034 | Rich home screen with hero banners, carousels, and personalized sections | High | Done |
|
||||
| UR-035 | View cast/crew (actors, directors) on movie/show detail pages | High | Done |
|
||||
| UR-036 | Navigate to actor/person page showing their filmography | Medium | Done |
|
||||
| UR-037 | Visually appealing video library with poster grids and metadata | High | Done |
|
||||
| UR-038 | Movie/show detail page with backdrop, ratings, and rich metadata | High | Done |
|
||||
| UR-039 | Navigate between main sections via bottom navigation bar | High | Done |
|
||||
|
||||
---
|
||||
|
||||
## 2. Software Requirements
|
||||
|
||||
### 2.1 Integration Requirements
|
||||
|
||||
External system integrations and platform-specific implementations.
|
||||
|
||||
| ID | Requirement | Category | Traces To | Status |
|
||||
|----|-------------|----------|-----------|--------|
|
||||
| IR-001 | Build system supporting multiple targets (Linux, Android) | Build | UR-001 | Done |
|
||||
| IR-002 | Build scripts for Android and Linux | Build | UR-001 | Done |
|
||||
| IR-003 | Integration of libmpv for Linux playback | Playback | UR-003, UR-004 | Done |
|
||||
| IR-004 | Integration of ExoPlayer for Android playback | Playback | UR-003, UR-004 | In Progress (basic playback works, audio settings missing) |
|
||||
| IR-005 | MPRIS D-Bus integration for Linux lockscreen/media controls | Platform | UR-006 | Planned |
|
||||
| IR-006 | Android MediaSession integration for lockscreen controls | Platform | UR-006 | Done |
|
||||
| IR-007 | Bluetooth AVRCP integration via system media session | Platform | UR-006 | Planned |
|
||||
| IR-008 | Android audio focus handling (pause on call) | Platform | UR-004, UR-006 | Done |
|
||||
| IR-009 | Jellyfin API client for authentication | API | UR-009, UR-012 | Done |
|
||||
| IR-010 | Jellyfin API client for library browsing | API | UR-007, UR-008 | Done |
|
||||
| IR-011 | Jellyfin API client for playback streaming | API | UR-003, UR-004 | Done |
|
||||
| IR-012 | Jellyfin Sessions API for remote playback control | API | UR-010 | Done |
|
||||
| IR-021 | Android MediaRouter integration for remote volume in system panel | Platform | UR-010, UR-016 | Planned |
|
||||
| IR-013 | SQLite integration for local database | Storage | UR-002, UR-011 | Done |
|
||||
| IR-014 | Secure credential storage (keyring/keychain) | Security | UR-012 | Done |
|
||||
| IR-015 | Jellyfin API client for playback progress reporting | API | UR-019, UR-025 | Done |
|
||||
| IR-016 | Jellyfin API client for subtitle/audio track info | API | UR-020, UR-021 | Done |
|
||||
| IR-017 | Jellyfin API client for transcoding parameters | API | UR-022 | Planned |
|
||||
| IR-018 | libmpv subtitle rendering and selection | Playback | UR-020 | Planned |
|
||||
| IR-019 | libmpv audio track selection | Playback | UR-021 | Planned |
|
||||
| IR-020 | libmpv/ExoPlayer equalizer integration | Playback | UR-027 | Planned |
|
||||
| IR-022 | Jellyfin API client for person/cast data | API | UR-035, UR-036 | Done |
|
||||
| IR-023 | Database schema for person/cast caching | Storage | UR-035, UR-036 | Done |
|
||||
| IR-024 | Jellyfin API client for home screen data (featured, continue watching) | API | UR-034 | Done |
|
||||
|
||||
### 2.2 Jellyfin API Requirements
|
||||
|
||||
API endpoints and data contracts required for Jellyfin integration.
|
||||
|
||||
| ID | Requirement | Endpoint Category | Traces To | Status |
|
||||
|----|-------------|-------------------|-----------|--------|
|
||||
| JA-001 | Server connection and discovery | System | UR-009 | Done |
|
||||
| JA-002 | User authentication (username/password) | Users | UR-009, UR-012 | Done |
|
||||
| JA-003 | Get user library views | UserViews | UR-007 | Done |
|
||||
| JA-004 | Get library items (paginated) | Items | UR-007 | Done |
|
||||
| JA-005 | Get item details and metadata | Items | UR-007 | Done |
|
||||
| JA-006 | Search across libraries | Items | UR-008 | Done |
|
||||
| JA-007 | Get playback info and stream URL | MediaInfo | UR-003, UR-004 | Done |
|
||||
| JA-008 | Get available subtitles for item | MediaInfo | UR-020 | Done |
|
||||
| JA-009 | Get available audio tracks for item | MediaInfo | UR-021 | Done |
|
||||
| JA-010 | Report playback start | Sessions | UR-025 | Done |
|
||||
| JA-011 | Report playback progress (periodic) | Sessions | UR-025 | Done |
|
||||
| JA-012 | Report playback stopped | Sessions | UR-025 | Done |
|
||||
| JA-013 | Get resume position for item | UserData | UR-019 | Done |
|
||||
| JA-014 | Get "Next Up" items | Shows | UR-023 | Done |
|
||||
| JA-015 | Get "Continue Watching" items | Items | UR-023 | Done |
|
||||
| JA-016 | Get recently added items | Items | UR-024 | Done |
|
||||
| JA-017 | Mark item as favorite | UserData | UR-017 | Done |
|
||||
| JA-018 | Remove item from favorites | UserData | UR-017 | Done |
|
||||
| JA-019 | Get/create/update playlists | Playlists | UR-014 | Done |
|
||||
| JA-020 | Add/remove items from playlist | Playlists | UR-014 | Done |
|
||||
| JA-021 | Get active sessions list | Sessions | UR-010 | Done |
|
||||
| JA-022 | Send playback commands to remote session (play/pause/stop) | Sessions | UR-010 | Done |
|
||||
| JA-023 | Send seek command to remote session | Sessions | UR-010 | Done |
|
||||
| JA-024 | Send next/previous track commands to remote session | Sessions | UR-010 | Done |
|
||||
| JA-025 | Play specific item on remote session | Sessions | UR-010 | Done |
|
||||
| JA-026 | Send volume/mute commands to remote session | Sessions | UR-010 | Done |
|
||||
| JA-027 | Get transcoding options | MediaInfo | UR-022 | Planned |
|
||||
| JA-028 | Get image/artwork URLs | Images | UR-007 | Done |
|
||||
| JA-029 | Get cast/crew for item (actors, directors) | Items | UR-035 | Done |
|
||||
| JA-030 | Get person details and filmography | Persons | UR-036 | Done |
|
||||
| JA-031 | Get items by person (actor/director filmography) | Items | UR-036 | Done |
|
||||
|
||||
### 2.3 Development Requirements
|
||||
|
||||
Internal architecture, components, and application logic.
|
||||
|
||||
| ID | Requirement | Category | Traces To | Status |
|
||||
|----|-------------|----------|-----------|--------|
|
||||
| DR-001 | Player state machine (idle, loading, playing, paused, seeking, error) | Player | UR-005 | Done |
|
||||
| DR-002 | MediaItem struct tracking source, location, duration, metadata | Player | UR-003, UR-004 | Done |
|
||||
| DR-003 | Source-agnostic media abstraction (Remote, Local, DirectUrl) | Player | UR-002, UR-011 | Done |
|
||||
| DR-004 | PlayerBackend trait for platform-agnostic playback | Player | UR-003, UR-004 | Done |
|
||||
| DR-005 | Queue manager with shuffle, repeat, history | Player | UR-005, UR-015 | Done |
|
||||
| DR-006 | Audio pre-caching for seamless track transitions | Player | UR-004 | Planned |
|
||||
| DR-007 | Library browsing screens (grid view, search, filters) | UI | UR-007, UR-008 | Done |
|
||||
| DR-008 | Album/Series detail view with track listing | UI | UR-007 | Done |
|
||||
| DR-009 | Audio player UI (mini player, full screen) | UI | UR-005 | Done |
|
||||
| DR-010 | Video player UI (fullscreen, controls overlay) | UI | UR-003, UR-005 | Done |
|
||||
| DR-011 | Search bar with cross-library search | UI | UR-008 | Done |
|
||||
| DR-012 | Local database for media metadata cache | Storage | UR-002 | Done |
|
||||
| DR-013 | Repository pattern for online/offline data access | Storage | UR-002 | Done |
|
||||
| DR-014 | Offline mutation queue for sync-back operations | Storage | UR-002, UR-014, UR-017 | Done |
|
||||
| DR-015 | Download manager with queue and progress tracking | Storage | UR-011, UR-018 | Done |
|
||||
| DR-016 | Thumbnail caching and sync with server | Storage | UR-007 | Done |
|
||||
| DR-017 | "Manage Downloads" screen for local media management | UI | UR-013 | Done |
|
||||
| DR-018 | Download buttons on library/album/player screens | UI | UR-011, UR-018 | Done |
|
||||
| DR-019 | Playlist creation and editing UI | UI | UR-014 | Done |
|
||||
| DR-020 | Queue management UI (add, remove, reorder) | UI | UR-015 | Done |
|
||||
| DR-021 | Like/favorite functionality on media items | UI | UR-017 | Done |
|
||||
| DR-022 | Resume position tracking and restoration on play | Player | UR-019 | Done |
|
||||
| DR-023 | Subtitle selection UI in video player | UI | UR-020 | Done |
|
||||
| DR-024 | Audio track selection UI in video player | UI | UR-021 | Done |
|
||||
| DR-025 | Quality/transcoding settings UI | UI | UR-022 | Planned |
|
||||
| DR-026 | "Continue Watching" / "Next Up" home section | UI | UR-023 | Done |
|
||||
| DR-027 | "Recently Added" home section | UI | UR-024 | Done |
|
||||
| DR-028 | Playback progress sync service (periodic reporting) | Player | UR-025 | Done |
|
||||
| DR-029 | Sleep timer with roller UI, time/track/episode modes, and auto-stop (audio + video players) | Player | UR-026 | Done |
|
||||
| DR-049 | Auto-play episode limit (configurable max episodes per session) | Player | UR-023 | Done |
|
||||
| DR-050 | Reusable scroll picker (roller) component | UI | UR-026 | Done |
|
||||
| DR-030 | Equalizer UI with presets and custom bands | UI | UR-027 | Planned |
|
||||
| DR-031 | Clickable artist/album links in now playing view | UI | UR-028 | Done |
|
||||
| DR-032 | List view option for library browsing (albums, artists) | UI | UR-029 | Done |
|
||||
| DR-033 | Genre browsing screen with quick filters | UI | UR-030 | Done |
|
||||
| DR-034 | Crossfade engine with configurable duration (0-12s) | Player | UR-031 | Done (Linux only) |
|
||||
| DR-035 | Gapless playback between sequential tracks | Player | UR-032 | Done (Linux only) |
|
||||
| DR-036 | Volume normalization with preset levels (Loud/Normal/Quiet) | Player | UR-033 | Done (Linux only) |
|
||||
| DR-037 | Remote session browser and control UI | UI | UR-010 | Done |
|
||||
| DR-038 | Home screen with hero banner carousel (featured/continue watching) | UI | UR-034 | Done |
|
||||
| DR-039 | Home screen horizontal carousels (recently added, recommendations) | UI | UR-034, UR-024 | Done |
|
||||
| DR-040 | Cast/crew section on movie/show detail pages | UI | UR-035 | Done |
|
||||
| DR-041 | Person/actor detail page with filmography grid | UI | UR-036 | Done |
|
||||
| DR-042 | Video library grid with poster cards, year, and rating badges | UI | UR-037 | Done |
|
||||
| DR-043 | Movie/show detail page with backdrop hero, synopsis, and metadata | UI | UR-038 | Done |
|
||||
| DR-044 | Horizontal scrolling actor/cast row with profile images | UI | UR-035 | Done |
|
||||
| DR-045 | Bottom navigation bar with Home, Library, Search buttons | UI | UR-039 | Done |
|
||||
| DR-046 | Dedicated search page with input and results | UI | UR-039 | Done |
|
||||
| DR-047 | Next episode auto-play popup with configurable countdown and episode limit | Player | UR-023 | Done |
|
||||
| DR-048 | Video settings (auto-play toggle, countdown duration, episode limit) | Settings | UR-023, UR-026 | Done |
|
||||
|
||||
---
|
||||
|
||||
## 3. Traceability Matrix
|
||||
|
||||
### User Requirements to Software Requirements
|
||||
|
||||
| User Req | Integration Requirements | Development Requirements |
|
||||
|----------|-------------------------|-------------------------|
|
||||
| UR-001 | IR-001, IR-002 | - |
|
||||
| UR-002 | IR-013 | DR-003, DR-012, DR-013, DR-014 |
|
||||
| UR-003 | IR-003, IR-004, IR-011 | DR-002, DR-004, DR-010 |
|
||||
| UR-004 | IR-003, IR-004, IR-008, IR-011 | DR-002, DR-004, DR-006 |
|
||||
| UR-005 | - | DR-001, DR-005, DR-009 |
|
||||
| UR-006 | IR-005, IR-006, IR-007, IR-008 | - |
|
||||
| UR-007 | IR-010 | DR-007, DR-008, DR-016 |
|
||||
| UR-008 | IR-010 | DR-007, DR-011 |
|
||||
| UR-009 | IR-009, IR-010, IR-011 | - |
|
||||
| UR-010 | IR-012, IR-021 | DR-037 |
|
||||
| UR-011 | IR-013 | DR-003, DR-015, DR-018 |
|
||||
| UR-012 | IR-009, IR-014 | - |
|
||||
| UR-013 | IR-013 | DR-017 |
|
||||
| UR-014 | IR-010 | DR-014, DR-019 |
|
||||
| UR-015 | - | DR-005, DR-020 |
|
||||
| UR-016 | - | - |
|
||||
| UR-017 | - | DR-014, DR-021 |
|
||||
| UR-018 | IR-013 | DR-015, DR-018 |
|
||||
| UR-019 | IR-015 | DR-022 |
|
||||
| UR-020 | IR-016, IR-018 | DR-023 |
|
||||
| UR-021 | IR-016, IR-019 | DR-024 |
|
||||
| UR-022 | IR-017 | DR-025 |
|
||||
| UR-023 | IR-010 | DR-026, DR-047, DR-048, DR-049 |
|
||||
| UR-024 | IR-010 | DR-027 |
|
||||
| UR-025 | IR-015 | DR-028 |
|
||||
| UR-026 | - | DR-029, DR-048, DR-050 |
|
||||
| UR-027 | IR-020 | DR-030 |
|
||||
| UR-028 | - | DR-031 |
|
||||
| UR-029 | - | DR-032 |
|
||||
| UR-030 | IR-010 | DR-033 |
|
||||
| UR-031 | - | DR-034 |
|
||||
| UR-032 | - | DR-035 |
|
||||
| UR-033 | - | DR-036 |
|
||||
| UR-034 | IR-010, IR-024 | DR-038, DR-039 |
|
||||
| UR-035 | IR-022, IR-023 | DR-040, DR-044 |
|
||||
| UR-036 | IR-022, IR-023 | DR-041 |
|
||||
| UR-037 | IR-010 | DR-042 |
|
||||
| UR-038 | IR-010 | DR-043 |
|
||||
| UR-039 | - | DR-045, DR-046 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Test Traceability
|
||||
|
||||
### Unit Tests to Software Requirements
|
||||
|
||||
| Test ID | Test Description | Traces To | Status |
|
||||
|---------|-----------------|-----------|--------|
|
||||
| UT-001 | Player state transitions | DR-001 | Pending |
|
||||
| UT-002 | MediaItem source URL resolution | DR-002, DR-003 | Pending |
|
||||
| UT-003 | Queue next/previous navigation | DR-005 | Pending |
|
||||
| UT-004 | Queue shuffle order generation | DR-005 | Pending |
|
||||
| UT-005 | Queue repeat mode behavior | DR-005 | Pending |
|
||||
| UT-006 | Jellyfin authentication flow | IR-009 | Pending |
|
||||
| UT-007 | Jellyfin library items parsing | IR-010 | Pending |
|
||||
| UT-008 | Repository pattern online/offline switching | DR-013 | Pending |
|
||||
| UT-009 | Offline mutation queue persistence | DR-014 | Pending |
|
||||
| UT-010 | Download queue management | DR-015 | Done |
|
||||
| UT-011 | Resume position storage and retrieval | DR-022 | Pending |
|
||||
| UT-012 | Sleep timer countdown logic | DR-029 | Pending |
|
||||
| UT-013 | Playback progress reporting throttling | DR-028 | Pending |
|
||||
| UT-014 | Database open and in-memory mode | IR-013, DR-012 | Done |
|
||||
| UT-015 | Database migrations run successfully | IR-013, DR-012 | Done |
|
||||
| UT-016 | All database tables created | IR-013, DR-012 | Done |
|
||||
| UT-017 | FTS5 search table created | IR-013, DR-012 | Done |
|
||||
| UT-018 | Server CRUD operations | IR-013, DR-012 | Done |
|
||||
| UT-019 | User CRUD operations | IR-013, DR-012 | Done |
|
||||
| UT-020 | Cascade delete server removes users | IR-013, DR-012 | Done |
|
||||
| UT-021 | Item insert and FTS search | IR-013, DR-012 | Done |
|
||||
| UT-022 | User data playback position storage | IR-013, DR-012, DR-022 | Done |
|
||||
| UT-023 | Sync queue operations | IR-013, DR-014 | Done |
|
||||
| UT-024 | Downloads table operations | IR-013, DR-015 | Done |
|
||||
| UT-025 | Migrations are idempotent | IR-013, DR-012 | Done |
|
||||
| UT-026 | NullBackend volume default value | DR-004 | Done |
|
||||
| UT-027 | NullBackend set volume | DR-004 | Done |
|
||||
| UT-028 | NullBackend volume clamping (high/low) | DR-004 | Done |
|
||||
| UT-029 | NullBackend volume boundary values | DR-004 | Done |
|
||||
| UT-030 | PlayerController volume default | DR-004, DR-009 | Done |
|
||||
| UT-031 | PlayerController set volume | DR-004, DR-009 | Done |
|
||||
| UT-032 | PlayerController muted default | DR-004, DR-009 | Done |
|
||||
| UT-033 | PlayerController volume delegates to backend | DR-004, DR-009 | Done |
|
||||
| UT-034 | Download event serialization roundtrip | DR-015 | Done |
|
||||
| UT-035 | Download event completed serialization | DR-015 | Done |
|
||||
| UT-036 | Download event failed serialization | DR-015 | Done |
|
||||
| UT-037 | Download worker exponential backoff | DR-015 | Done |
|
||||
| UT-038 | Download worker error retryable check | DR-015 | Done |
|
||||
| UT-039 | Download manager creation | DR-015 | Done |
|
||||
| UT-040 | Download manager set max concurrent | DR-015 | Done |
|
||||
| UT-041 | Download info serialization | DR-015 | Done |
|
||||
| UT-042 | Download command filename sanitization | DR-015, DR-018 | Done |
|
||||
| UT-043 | Download command filename extension preservation | DR-015, DR-018 | Done |
|
||||
| UT-044 | Offline item serialization | DR-017 | Done |
|
||||
| UT-045 | Smart cache default config | DR-015 | Done |
|
||||
| UT-046 | Smart cache album affinity tracking | DR-015 | Done |
|
||||
| UT-047 | Smart cache queue precache config | DR-015 | Done |
|
||||
| UT-048 | Smart cache storage limit check | DR-015 | Done |
|
||||
| UT-049 | Playlist create (offline) | DR-019, JA-019 | Done |
|
||||
| UT-050 | Playlist delete (offline) | DR-019, JA-019 | Done |
|
||||
| UT-051 | Playlist rename (offline) | DR-019, JA-019 | Done |
|
||||
| UT-052 | Playlist get items (offline) | DR-019, JA-019 | Done |
|
||||
| UT-053 | Playlist add items (offline) | DR-019, JA-020 | Done |
|
||||
| UT-054 | Playlist remove items (offline) | DR-019, JA-020 | Done |
|
||||
| UT-055 | Playlist reorder items (offline) | DR-019, JA-020 | Done |
|
||||
| UT-056 | Playlist entry serialization | DR-019, JA-019 | Done |
|
||||
| UT-057 | Playlist Tauri command param naming (camelCase) | DR-019, JA-019, JA-020 | Done |
|
||||
| UT-058 | Playlist repository client methods | DR-019, JA-019, JA-020 | Done |
|
||||
|
||||
### Integration Tests
|
||||
|
||||
| Test ID | Test Description | Traces To | Status |
|
||||
|---------|-----------------|-----------|--------|
|
||||
| IT-001 | End-to-end authentication with Jellyfin server | IR-009, UR-009 | Pending |
|
||||
| IT-002 | Library browsing and item loading | IR-010, UR-007 | Pending |
|
||||
| IT-003 | Audio playback via libmpv | IR-003, UR-004 | Pending |
|
||||
| IT-004 | Video playback via libmpv | IR-003, UR-003 | Pending |
|
||||
| IT-005 | MPRIS lockscreen controls on Linux | IR-005, UR-006 | Pending |
|
||||
| IT-006 | Offline mode with local database | IR-013, UR-002 | Pending |
|
||||
| IT-007 | Media download and local playback | DR-015, UR-011 | Pending |
|
||||
| IT-008 | Subtitle track selection via libmpv | IR-018, UR-020 | Pending |
|
||||
| IT-009 | Audio track selection via libmpv | IR-019, UR-021 | Pending |
|
||||
| IT-010 | Playback progress sync to Jellyfin | IR-015, UR-025 | Pending |
|
||||
| IT-011 | Resume playback from server position | IR-015, UR-019 | Pending |
|
||||
| IT-012 | Equalizer bands via libmpv | IR-020, UR-027 | Pending |
|
||||
|
||||
---
|
||||
|
||||
## 5. Technical Debt
|
||||
|
||||
### Linux Keyring Integration Workaround
|
||||
|
||||
**Issue**: The `keyring-rs` crate (v3.x) has issues with retrieving credentials from the Linux Secret Service API, despite successfully saving them.
|
||||
|
||||
**Symptoms**:
|
||||
- Credentials are saved to the system keyring successfully (verified with `secret-tool search`)
|
||||
- Retrieval via the `keyring-rs` library fails with `NoEntry` error
|
||||
- Session restoration fails on app restart even though credentials exist
|
||||
|
||||
**Root Cause**:
|
||||
The `keyring-rs` library's Linux backend doesn't correctly retrieve entries from the Secret Service that it previously stored. This appears to be a bug in how the library interfaces with the Secret Service D-Bus API.
|
||||
|
||||
**Current Workaround**:
|
||||
We bypass the `keyring-rs` library on Linux and use direct system calls to `secret-tool`:
|
||||
- **Save**: `secret-tool store --label <label> service <service> username <username>`
|
||||
- **Retrieve**: `secret-tool lookup service <service> username <username>`
|
||||
- **Delete**: `secret-tool clear service <service> username <username>`
|
||||
|
||||
**Implementation**:
|
||||
See [src-tauri/src/credentials.rs](../src-tauri/src/credentials.rs) for the
|
||||
Linux-specific `secret-tool` save/get/delete paths.
|
||||
|
||||
**Future Fix**:
|
||||
- Monitor `keyring-rs` for bug fixes in future versions
|
||||
- Consider alternative secure storage libraries
|
||||
- Test if newer versions of `keyring-rs` (v4.x+) resolve the issue
|
||||
- Once fixed, remove the Linux-specific workaround and use the cross-platform `keyring-rs` API
|
||||
|
||||
**Impact**:
|
||||
- Low - The workaround is functionally equivalent to proper keyring integration
|
||||
- Credentials are stored securely in the system keyring
|
||||
- Session restoration works correctly
|
||||
- Only affects Linux; macOS and Windows use the standard `keyring-rs` implementation
|
||||
|
||||
**Dependencies**:
|
||||
- Requires `secret-tool` to be installed on Linux systems (part of `libsecret-tools` package)
|
||||
- Already available on most Linux distributions by default
|
||||
|
||||
---
|
||||
|
||||
### Platform Playback Backend Parity (Linux vs Android)
|
||||
|
||||
**Issue**: The Linux (MPV) and Android (ExoPlayer) playback backends have diverged in feature implementation and architecture patterns.
|
||||
|
||||
**Symptoms**:
|
||||
- Audio settings (crossfade, gapless playback, volume normalization) work on Linux but not on Android
|
||||
- Position update frequency differs between platforms (Linux: 250ms polling, Android: on-demand callbacks)
|
||||
- Thread safety models differ (Linux: `Arc<Mutex<>>`, Android: global `OnceLock` statics)
|
||||
|
||||
**Root Cause**:
|
||||
The `PlayerBackend` trait defines optional audio settings methods with default empty implementations. The Linux `MpvBackend` overrides these with full MPV property commands, but `ExoPlayerBackend` uses the defaults.
|
||||
|
||||
**Affected Files**:
|
||||
- [src-tauri/src/player/backend.rs](../src-tauri/src/player/backend.rs) - Trait with default empty implementations
|
||||
- [src-tauri/src/player/mpv_backend.rs](../src-tauri/src/player/mpv_backend.rs) - Full audio settings support
|
||||
- [src-tauri/src/player/android/mod.rs](../src-tauri/src/player/android/mod.rs) - Missing audio settings implementation
|
||||
|
||||
**Feature Parity Matrix**:
|
||||
|
||||
| Feature | Linux (MPV) | Android (ExoPlayer) | Status |
|
||||
|---------|-------------|---------------------|--------|
|
||||
| Basic playback | ✅ | ✅ | Parity |
|
||||
| Volume control | ✅ | ✅ | Parity |
|
||||
| Seek | ✅ | ✅ | Parity |
|
||||
| Crossfade | ✅ | ❌ | Gap |
|
||||
| Gapless playback | ✅ | ❌ | Gap |
|
||||
| Volume normalization | ✅ | ❌ | Gap |
|
||||
| Position updates | 250ms | On-demand | Inconsistent |
|
||||
|
||||
**Future Fix**:
|
||||
1. Implement `set_audio_settings()` in `ExoPlayerBackend`
|
||||
2. Add Kotlin-side ExoPlayer configuration for crossfade (using `ConcatenatingMediaSource` or `DefaultMediaSourceFactory`)
|
||||
3. Implement gapless via ExoPlayer's built-in gapless support
|
||||
4. Add volume normalization via ExoPlayer's `LoudnessEnhancer` or audio processor
|
||||
5. Standardize position update frequency across platforms
|
||||
|
||||
**Impact**:
|
||||
- Medium - Android users lack audio enhancement features advertised in requirements
|
||||
- User experience differs between platforms
|
||||
- UR-031 (Crossfade), UR-032 (Gapless), UR-033 (Normalization) only work on Linux
|
||||
|
||||
**Traces To**: IR-004, UR-031, UR-032, UR-033, DR-034, DR-035, DR-036
|
||||
|
||||
---
|
||||
|
||||
### Frontend Playback Code Duplication
|
||||
|
||||
**Issue**: Playback control handlers and state derivations are duplicated between `AudioPlayer.svelte` and `MiniPlayer.svelte`.
|
||||
|
||||
**Symptoms**:
|
||||
- Identical try-catch wrapped handler functions in both components (~44 lines duplicated)
|
||||
- Same `$derived` state merging logic for local/remote playback in both components
|
||||
- Position conversion (ticks ↔ seconds) scattered across multiple files
|
||||
|
||||
**Affected Files**:
|
||||
- [src/lib/components/player/AudioPlayer.svelte](../src/lib/components/player/AudioPlayer.svelte) - Duplicate handlers
|
||||
- [src/lib/components/player/MiniPlayer.svelte](../src/lib/components/player/MiniPlayer.svelte) - Duplicate handlers
|
||||
- [src/lib/services/playbackControl.ts](../src/lib/services/playbackControl.ts) - Position conversion
|
||||
- [src/lib/stores/playbackMode.ts](../src/lib/stores/playbackMode.ts) - Position conversion
|
||||
- [src/lib/services/playbackReporting.ts](../src/lib/services/playbackReporting.ts) - Position conversion
|
||||
|
||||
**Duplicated Code**:
|
||||
```typescript
|
||||
// These handlers are identical in both AudioPlayer and MiniPlayer:
|
||||
handlePlayPause(), handleNext(), handlePrevious(),
|
||||
handleToggleShuffle(), handleCycleRepeat(), handleVolumeChange()
|
||||
|
||||
// These derived states use identical logic:
|
||||
displayMedia, displayIsPlaying, displayPosition, displayDuration
|
||||
```
|
||||
|
||||
**Future Fix**:
|
||||
1. Create `src/lib/utils/playbackUnits.ts`:
|
||||
```typescript
|
||||
export const TICKS_PER_SECOND = 10_000_000;
|
||||
export const secondsToTicks = (s: number) => Math.floor(s * TICKS_PER_SECOND);
|
||||
export const ticksToSeconds = (t: number) => t / TICKS_PER_SECOND;
|
||||
```
|
||||
|
||||
2. Create `src/lib/composables/useMergedPlaybackState.svelte.ts`:
|
||||
- Export `displayMedia`, `displayIsPlaying`, `displayPosition`, `displayDuration`
|
||||
- Single source of truth for merged local/remote state
|
||||
|
||||
3. Simplify handler wrappers using a utility:
|
||||
```typescript
|
||||
export const withErrorHandler = (fn: () => Promise<void>, context: string) =>
|
||||
async () => { try { await fn(); } catch (e) { console.error(`${context}:`, e); } };
|
||||
```
|
||||
|
||||
**Impact**:
|
||||
- Low - Code works correctly but violates DRY principle
|
||||
- Maintenance burden when logic needs to change
|
||||
- Risk of handlers diverging over time
|
||||
|
||||
**Traces To**: DR-009
|
||||
@@ -0,0 +1,212 @@
|
||||
# TRACES Quick Reference Guide
|
||||
|
||||
## What are TRACES?
|
||||
|
||||
TRACES are requirement identifiers embedded in code comments to track which requirements are implemented where.
|
||||
|
||||
Format: `// TRACES: UR-001, UR-002 | DR-003`
|
||||
|
||||
## Quick Examples
|
||||
|
||||
### TypeScript
|
||||
```typescript
|
||||
// TRACES: UR-005, UR-026 | DR-029
|
||||
export function handlePlayback() { }
|
||||
|
||||
/**
|
||||
* Resume playback from saved position
|
||||
* TRACES: UR-019 | DR-022
|
||||
*/
|
||||
export async function resumePlayback(itemId: string) { }
|
||||
```
|
||||
|
||||
### Svelte
|
||||
```svelte
|
||||
<!-- TRACES: UR-007, UR-008 | DR-007 -->
|
||||
<script>
|
||||
export let items = [];
|
||||
</script>
|
||||
```
|
||||
|
||||
### Rust
|
||||
```rust
|
||||
/// TRACES: UR-005 | DR-001
|
||||
pub enum PlayerState { ... }
|
||||
|
||||
#[test]
|
||||
fn test_queue_next() {
|
||||
// TRACES: UR-005 | DR-005 | UT-003
|
||||
}
|
||||
```
|
||||
|
||||
## Requirement Types
|
||||
|
||||
| Type | Meaning | Example |
|
||||
|------|---------|---------|
|
||||
| **UR** | User Requirement | UR-005: Control media playback |
|
||||
| **IR** | Integration Requirement | IR-003: LibMPV integration |
|
||||
| **DR** | Development Requirement | DR-001: Player state machine |
|
||||
| **JA** | Jellyfin API Requirement | JA-007: Get playback info |
|
||||
| **UT** | Unit Test | UT-001: Player state transitions |
|
||||
| **IT** | Integration Test | IT-003: Audio playback via libmpv |
|
||||
|
||||
## Where to Find Requirements
|
||||
|
||||
1. **User Requirements (UR):** [README.md](README.md#1-user-requirements)
|
||||
2. **Integration Requirements (IR):** [README.md](README.md#21-integration-requirements)
|
||||
3. **Development Requirements (DR):** [README.md](README.md#23-development-requirements)
|
||||
4. **Jellyfin API (JA):** [README.md](README.md#22-jellyfin-api-requirements)
|
||||
|
||||
## How to Add TRACES
|
||||
|
||||
### Step 1: Find the Requirement
|
||||
Look up the requirement in README.md or the traceability matrix.
|
||||
|
||||
Example: `UR-005: Control media playback (pause, play, skip, scrub)`
|
||||
|
||||
### Step 2: Add Comment
|
||||
Add TRACES comment at the top of the function/type/module:
|
||||
|
||||
```typescript
|
||||
// TRACES: UR-005
|
||||
export async function playMedia(itemId: string) {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Run Extraction
|
||||
Verify the trace is captured:
|
||||
|
||||
```bash
|
||||
bun run traces:json | jq '.requirements | keys | grep "UR-005"'
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Single Requirement
|
||||
```typescript
|
||||
// TRACES: UR-005
|
||||
function handlePlay() { }
|
||||
```
|
||||
|
||||
### Multiple Requirements, Same Type
|
||||
```typescript
|
||||
// TRACES: UR-005, UR-026, UR-019
|
||||
function handlePlaybackState() { }
|
||||
```
|
||||
|
||||
### Multiple Types
|
||||
```typescript
|
||||
// TRACES: UR-005, UR-026 | DR-029
|
||||
function autoplayNextEpisode() { }
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
```typescript
|
||||
// TRACES: UR-005 | UT-001
|
||||
#[test]
|
||||
fn test_player_state_transition() { }
|
||||
```
|
||||
|
||||
### Modules/Files
|
||||
```typescript
|
||||
/**
|
||||
* Player event handling
|
||||
* TRACES: UR-005, UR-019, UR-023 | DR-001, DR-028
|
||||
*/
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
### Check Your Changes
|
||||
```bash
|
||||
# View current coverage
|
||||
bun run traces:json | jq '.byType'
|
||||
|
||||
# Generate full report
|
||||
bun run traces:markdown
|
||||
|
||||
# Check specific requirement
|
||||
bun run traces:json | jq '.requirements."UR-005"'
|
||||
```
|
||||
|
||||
### Before Committing
|
||||
1. Ensure all new code has TRACES
|
||||
2. Format is correct: `// TRACES: ...`
|
||||
3. Requirements exist in README.md
|
||||
4. No typos in requirement IDs
|
||||
|
||||
## CI/CD Validation
|
||||
|
||||
The workflow automatically checks:
|
||||
- ✅ Coverage stays >= 50%
|
||||
- ✅ New files have TRACES
|
||||
- ✅ JSON format is valid
|
||||
- ✅ Reports are generated
|
||||
|
||||
See [traceability-ci.md](docs/traceability-ci.md) for details.
|
||||
|
||||
## Tips & Tricks
|
||||
|
||||
### Find Related Code
|
||||
```bash
|
||||
# Find all code tracing to UR-005
|
||||
bun run traces:json | jq '.requirements."UR-005"'
|
||||
|
||||
# List all tests
|
||||
bun run traces:json | jq '.requirements | keys | map(select(startswith("UT")))'
|
||||
```
|
||||
|
||||
### Update Your Editor
|
||||
|
||||
**VS Code:**
|
||||
```json
|
||||
{
|
||||
"editor.wordBasedSuggestions": false,
|
||||
"editor.suggest.custom": [
|
||||
{
|
||||
"name": "TRACES Format",
|
||||
"insertText": "// TRACES: $1",
|
||||
"insertTextRules": "InsertAsSnippet"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Find Untraced Code
|
||||
```bash
|
||||
# Files modified without TRACES
|
||||
git diff --name-only | xargs grep -L "TRACES:" | head -10
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Do I need TRACES on every function?**
|
||||
A: Only for code that implements requirements. Internal helpers don't need TRACES.
|
||||
|
||||
**Q: Can I use TRACES on multiple related functions?**
|
||||
A: Yes! Add at the file/module level or on individual functions.
|
||||
|
||||
**Q: What if code doesn't relate to any requirement?**
|
||||
A: Leave it untraced. TRACES are for requirement-driven development.
|
||||
|
||||
**Q: How often should I regenerate reports?**
|
||||
A: Automatically on push (CI/CD). Manually after changes: `bun run traces:markdown`
|
||||
|
||||
**Q: Can I trace to requirements that aren't implemented yet?**
|
||||
A: Yes! TRACES show your implementation plan.
|
||||
|
||||
## See Also
|
||||
|
||||
- [Full Traceability Matrix](docs/traceability.md)
|
||||
- [CI/CD Pipeline Guide](docs/traceability-ci.md)
|
||||
- [Requirements Specification](README.md)
|
||||
- [Extraction Script](scripts/README.md#extract-tracests)
|
||||
|
||||
---
|
||||
|
||||
**Quick Start:**
|
||||
1. Add `// TRACES: UR-XXX` to new code
|
||||
2. Run `bun run traces:markdown`
|
||||
3. Check `docs/traceability.md`
|
||||
4. Submit PR - workflow validates automatically!
|
||||
@@ -0,0 +1,907 @@
|
||||
# JellyTau UX Flows & Screen Transitions
|
||||
|
||||
This document describes the expected user experience flows, screen transitions, and navigation patterns in JellyTau.
|
||||
|
||||
---
|
||||
|
||||
## 1. Core Navigation Structure
|
||||
|
||||
### 1.1 Navigation System
|
||||
|
||||
JellyTau uses a unified navigation system with a bottom navigation bar visible on all platforms (mobile and desktop) and additional header navigation for desktop.
|
||||
|
||||
**Bottom Navigation Bar (All Platforms - DR-045, UR-039):**
|
||||
|
||||
The bottom navigation bar is the primary navigation and is **always visible** on all platforms (mobile and desktop) except when:
|
||||
- Full-screen video player is active
|
||||
- User is on the login screen
|
||||
|
||||
**Bottom Nav Structure:**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ [Home] [Library] [Search] │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Routes:**
|
||||
- **Home** → `/` (home page with carousels and featured content)
|
||||
- **Library** → `/library` (library selector showing all libraries)
|
||||
- **Search** → `/search` (dedicated search page)
|
||||
|
||||
**Note:** Available on both mobile and desktop for consistent navigation access.
|
||||
|
||||
**Header Navigation (Desktop):**
|
||||
|
||||
On desktop (md breakpoint and above), the header contains:
|
||||
- Logo (links to `/library`)
|
||||
- Navigation links: Home, Library, Downloads, Settings
|
||||
- Search bar (inline)
|
||||
- User menu: Username, Downloads icon, Logout button
|
||||
|
||||
**Mobile Navigation:**
|
||||
|
||||
On mobile, the header contains:
|
||||
- Logo
|
||||
- Three-dot overflow menu button (Android-style)
|
||||
- Overflow menu includes:
|
||||
- Downloads
|
||||
- Settings
|
||||
- Sign out
|
||||
|
||||
**Access Points Summary:**
|
||||
- **Downloads** → Desktop: nav link + icon; Mobile: overflow menu
|
||||
- **Settings** → Desktop: nav link; Mobile: overflow menu
|
||||
|
||||
---
|
||||
|
||||
## 2. Initial App Launch Flow
|
||||
|
||||
### 2.1 First-Time Launch
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Launch[App Launch] --> CheckAuth{Stored<br/>Credentials?}
|
||||
CheckAuth -->|No| LoginScreen[Login Screen<br/>/login]
|
||||
CheckAuth -->|Yes| AutoLogin[Auto-login]
|
||||
|
||||
LoginScreen --> EnterURL[Enter Server URL]
|
||||
EnterURL --> EnterCreds[Enter Username/Password]
|
||||
EnterCreds --> LoginSuccess{Success?}
|
||||
LoginSuccess -->|No| LoginError[Show Error]
|
||||
LoginError --> EnterCreds
|
||||
LoginSuccess -->|Yes| StoreToken[Store Token in Keyring]
|
||||
|
||||
AutoLogin --> TokenValid{Token Valid?}
|
||||
TokenValid -->|No| LoginScreen
|
||||
TokenValid -->|Yes| HomePage
|
||||
|
||||
StoreToken --> HomePage[Home Page<br/>/]
|
||||
```
|
||||
|
||||
**Screens:**
|
||||
1. **Login Screen** (`/login`)
|
||||
- Server URL input
|
||||
- Username input
|
||||
- Password input
|
||||
- "Remember me" checkbox (default: on)
|
||||
- Login button
|
||||
- No header, no bottom nav
|
||||
|
||||
2. **Home Page** (`/`)
|
||||
- Default landing page after successful login
|
||||
- Shows featured content, carousels, continue watching
|
||||
- No MiniPlayer visible (nothing playing yet)
|
||||
- Bottom nav: Home tab active
|
||||
- Header with navigation links
|
||||
|
||||
### 2.2 Subsequent Launches
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Launch[App Launch] --> LoadAuth[Load Stored Token]
|
||||
LoadAuth --> Validate{Token Valid?}
|
||||
Validate -->|Yes| RestoreState[Restore Last Screen]
|
||||
Validate -->|No| LoginScreen[Login Screen<br/>/login]
|
||||
|
||||
RestoreState --> CheckPlayer{Was Player<br/>Active?}
|
||||
CheckPlayer -->|Yes| ShowMiniPlayer[Show MiniPlayer<br/>at bottom]
|
||||
CheckPlayer -->|No| HideMiniPlayer[No MiniPlayer]
|
||||
|
||||
ShowMiniPlayer --> LastScreen[Last Active Screen<br/>with MiniPlayer]
|
||||
HideMiniPlayer --> HomePage[Home Page<br/>/]
|
||||
```
|
||||
|
||||
**State Restoration:**
|
||||
- Last viewed screen (route) is restored (defaults to `/` if none)
|
||||
- If audio was playing, MiniPlayer appears at bottom
|
||||
- Playback state is NOT automatically resumed (user must press play)
|
||||
- Queue is restored if it existed
|
||||
|
||||
---
|
||||
|
||||
## 3. Audio Playback Flows
|
||||
|
||||
### 3.1 Starting Audio Playback
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Start[User Action] --> Action{Action Type?}
|
||||
|
||||
Action -->|Click Track| TrackList[TrackList Component]
|
||||
Action -->|Click Album| AlbumDetail[Album Detail Page]
|
||||
Action -->|Click Play on Album| AlbumPlay[Play Album Button]
|
||||
|
||||
TrackList --> PlayTrack[Play Single Track]
|
||||
PlayTrack --> QueueAll[Queue All Filtered Tracks]
|
||||
|
||||
AlbumPlay --> PlayAlbum[Play All Album Tracks]
|
||||
PlayAlbum --> QueueAlbum[Queue Album Tracks]
|
||||
|
||||
QueueAll --> InvokePlay[invoke player_play_queue]
|
||||
QueueAlbum --> InvokePlay
|
||||
|
||||
InvokePlay --> PlayerStarts[Player State: Playing]
|
||||
PlayerStarts --> MiniAppears[MiniPlayer Slides Up<br/>from Bottom]
|
||||
|
||||
MiniAppears --> StayOnPage[User Stays on<br/>Current Screen]
|
||||
```
|
||||
|
||||
**Entry Points for Audio Playback:**
|
||||
1. **TrackList** (`/library/music/tracks`, `/library/music/albums/[id]`)
|
||||
- Click track number → Play track + queue all visible tracks
|
||||
- Clicking track #3 in an album → Play track 3, queue tracks 1-10
|
||||
|
||||
2. **Album Card** (grid views)
|
||||
- Click album → Navigate to album detail
|
||||
- Play button on card → Play album immediately
|
||||
|
||||
3. **Search Results**
|
||||
- Click track → Play track + queue search results
|
||||
- Click album → Navigate to album detail
|
||||
|
||||
**MiniPlayer Behavior:**
|
||||
- Slides up from bottom with animation (300ms)
|
||||
- Height: 64px on mobile, 80px on desktop
|
||||
- Shows: artwork, title, artist, play/pause, next, favorite
|
||||
- Stays visible on ALL screens (except video player)
|
||||
- Click anywhere on MiniPlayer → Navigate to full player
|
||||
|
||||
**Track Highlighting:**
|
||||
When audio is playing, the currently playing track is visually highlighted in track lists and album pages:
|
||||
- Subtle blue background tint
|
||||
- Left border accent in Jellyfin blue
|
||||
- Title text colored in Jellyfin blue
|
||||
- Desktop: Animated pulsing dots indicator next to title
|
||||
- Mobile: Play arrow (▶) inline with title
|
||||
- Highlight updates automatically when skipping to next/previous track
|
||||
|
||||
### 3.2 MiniPlayer → Full Player Transition
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Mini[MiniPlayer Visible] --> UserClick{User Action}
|
||||
|
||||
UserClick -->|Click MiniPlayer| NavFullPlayer[Navigate to<br/>/player/[id]]
|
||||
UserClick -->|Swipe Up| SwipeGesture[Swipe Gesture<br/>Planned]
|
||||
|
||||
NavFullPlayer --> FullPlayer[Full Audio Player Screen]
|
||||
SwipeGesture --> FullPlayer
|
||||
|
||||
FullPlayer --> ShowControls[Show Full Controls:<br/>- Large artwork<br/>- Progress bar<br/>- Volume slider<br/>- Queue button<br/>- Shuffle/Repeat<br/>- Favorite button]
|
||||
|
||||
ShowControls --> MiniHidden[MiniPlayer Hidden]
|
||||
```
|
||||
|
||||
**Full Player Screen** (`/player/[id]`)
|
||||
- **Header:** Song title, artist (clickable links to artist/album pages)
|
||||
- **Artwork:** Large album art (centered, dominant)
|
||||
- **Progress:** Seek bar with current time / total duration
|
||||
- **Controls:** Previous, Play/Pause, Next (large touch targets)
|
||||
- **Secondary Controls:** Shuffle, Repeat mode, Queue, Favorite
|
||||
- **Volume:** Volume slider
|
||||
- **Bottom Nav:** Still visible (can navigate away while playing)
|
||||
- **Back button:** Returns to previous screen, MiniPlayer reappears
|
||||
|
||||
### 3.3 Full Player → Back to Browsing
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
FullPlayer[Full Player Screen] --> UserAction{User Action}
|
||||
|
||||
UserAction -->|Back Button / Close| HistoryBack[window.history.back]
|
||||
UserAction -->|Bottom Nav Click| NavOther[Navigate to<br/>Other Screen]
|
||||
|
||||
HistoryBack --> PrevScreen[Return to Previous Screen<br/>in Browser History]
|
||||
NavOther --> NewScreen[Navigate to New Screen]
|
||||
|
||||
PrevScreen --> MiniReappears[MiniPlayer Slides Up<br/>from Bottom]
|
||||
NewScreen --> MiniReappears
|
||||
|
||||
MiniReappears --> PlaybackContinues[Playback Continues<br/>in Background]
|
||||
```
|
||||
|
||||
**Navigation Behavior:**
|
||||
- **Back Button:** Uses browser history (`window.history.back()`) to return to the previous page
|
||||
- **Expected behavior:** Returns user to the screen they were on before opening full player
|
||||
- **Example:** User browsing album → clicks track → full player opens → clicks back → returns to album
|
||||
|
||||
**Key UX Principles:**
|
||||
- **Playback Never Stops:** Navigating away from player does NOT stop playback
|
||||
- **MiniPlayer Persistence:** MiniPlayer visible on ALL screens (except video/login)
|
||||
- **Queue Preserved:** Current queue remains intact
|
||||
- **State Restoration:** Returning to full player shows same state (position, volume, etc.)
|
||||
- **Natural Navigation:** Back button behaves as expected (returns to previous page, not just closes modal)
|
||||
|
||||
---
|
||||
|
||||
## 4. Video Playback Flows
|
||||
|
||||
### 4.1 Starting Video Playback
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Start[User Action] --> Action{Action Type?}
|
||||
|
||||
Action -->|Click Movie| MovieDetail[Movie Detail Page]
|
||||
Action -->|Click Episode| EpisodeClick[Episode Click]
|
||||
Action -->|Click Play Button| PlayButton[Play Button]
|
||||
|
||||
MovieDetail --> PlayMovie[Play Movie Button]
|
||||
EpisodeClick --> PlayEpisode[Play Episode]
|
||||
|
||||
PlayMovie --> CheckResume{Resume<br/>Position?}
|
||||
PlayEpisode --> CheckResume
|
||||
|
||||
CheckResume -->|Yes, >30s| ShowDialog[Resume Dialog]
|
||||
CheckResume -->|No| DirectPlay[Start from Beginning]
|
||||
|
||||
ShowDialog --> UserChoice{User Choice}
|
||||
UserChoice -->|Resume| ResumePlay[Start at Saved Position]
|
||||
UserChoice -->|Start Over| DirectPlay
|
||||
|
||||
ResumePlay --> FullscreenVideo[Fullscreen Video Player<br/>/player/[id]]
|
||||
DirectPlay --> FullscreenVideo
|
||||
|
||||
FullscreenVideo --> HideUI[Hide All UI:<br/>- No Bottom Nav<br/>- No MiniPlayer<br/>- Fullscreen only]
|
||||
```
|
||||
|
||||
**Resume Dialog:**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Continue Watching? │
|
||||
│ │
|
||||
│ [Movie Title] │
|
||||
│ Resume from 12:34 / 1:45:00 │
|
||||
│ │
|
||||
│ [Start from Beginning] [Resume] │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 Video Player Screen (IR-003, IR-004, UR-003)
|
||||
|
||||
**Initial State (First 3 seconds):**
|
||||
- Controls visible overlay
|
||||
- Top bar: Back button, title
|
||||
- Bottom bar: Play/Pause, seek bar, time, settings (subtitles, audio track)
|
||||
- Center: Large play/pause button
|
||||
|
||||
**After 3 Seconds (Idle):**
|
||||
- All controls fade out (500ms animation)
|
||||
- Fullscreen video only
|
||||
- System UI hidden (status bar, nav bar)
|
||||
|
||||
**User Interaction:**
|
||||
- **Tap screen:** Controls reappear for 3 seconds
|
||||
- **Double tap left side:** Rewind 10 seconds (shows animated feedback with "-10" indicator)
|
||||
- **Double tap right side:** Forward 10 seconds (shows animated feedback with "+10" indicator)
|
||||
- **Swipe up/down on left side:** Adjust brightness (0.3-1.7x, shows brightness indicator with progress bar)
|
||||
- **Swipe up/down on right side:** Adjust volume (0-100%, shows volume indicator with progress bar)
|
||||
- **Keyboard arrows:** ← rewind 10s, → forward 10s (desktop/external keyboard)
|
||||
- **Keyboard space/K:** Toggle play/pause
|
||||
- **Keyboard F:** Toggle fullscreen
|
||||
- **Pinch:** Zoom (planned)
|
||||
|
||||
### 4.3 Exiting Video Player
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
VideoPlaying[Video Playing] --> UserAction{User Action}
|
||||
|
||||
UserAction -->|Back Button| StopVideo[Stop Playback]
|
||||
UserAction -->|Home Button| Background[App to Background]
|
||||
UserAction -->|Video Ends| VideoEnd[Playback Ended]
|
||||
|
||||
StopVideo --> SaveProgress[Save Progress<br/>to Local DB + Server]
|
||||
VideoEnd --> SaveComplete[Mark as Watched<br/>Save Progress]
|
||||
Background --> PauseVideo[Pause Video]
|
||||
|
||||
SaveProgress --> ExitFullscreen[Exit Fullscreen]
|
||||
SaveComplete --> AutoNext{Next Episode<br/>Available?}
|
||||
|
||||
AutoNext -->|Yes| ShowCountdown[Show Countdown<br/>Next in 5s...]
|
||||
AutoNext -->|No| ExitFullscreen
|
||||
|
||||
ShowCountdown --> UserCancel{User Cancels?}
|
||||
UserCancel -->|Yes| ExitFullscreen
|
||||
UserCancel -->|No, timeout| PlayNext[Play Next Episode]
|
||||
|
||||
ExitFullscreen --> RestoreUI[Restore UI:<br/>- Bottom Nav<br/>- Previous Screen]
|
||||
|
||||
PlayNext --> VideoPlaying
|
||||
|
||||
PauseVideo --> ShowNotification[Show Notification:<br/>Tap to Resume]
|
||||
```
|
||||
|
||||
**Auto-Next Overlay:**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ │
|
||||
│ [Episode Thumbnail] │
|
||||
│ │
|
||||
│ Next: S01E02 - Episode Title │
|
||||
│ Starting in 5 seconds... │
|
||||
│ │
|
||||
│ [Cancel] [Play Now] │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Music Library Navigation Flows
|
||||
|
||||
### 5.1 Music Category Landing Page
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
LibraryHome[Library Home<br/>/library] --> ClickMusic[Click Music Library]
|
||||
|
||||
ClickMusic --> MusicLanding[Music Landing Page<br/>/library/music]
|
||||
|
||||
MusicLanding --> ShowCategories[Show Category Cards:<br/>- Tracks<br/>- Artists<br/>- Albums<br/>- Playlists<br/>- Genres]
|
||||
|
||||
ShowCategories --> UserClick{User Clicks Category}
|
||||
|
||||
UserClick -->|Tracks| TracksPage[All Tracks Page<br/>/library/music/tracks]
|
||||
UserClick -->|Artists| ArtistsPage[Artists Grid<br/>/library/music/artists]
|
||||
UserClick -->|Albums| AlbumsPage[Albums Grid<br/>/library/music/albums]
|
||||
UserClick -->|Playlists| PlaylistsPage[Playlists Grid<br/>/library/music/playlists]
|
||||
UserClick -->|Genres| GenresPage[Genres Browser<br/>/library/music/genres]
|
||||
```
|
||||
|
||||
**Category Cards:**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ ┌──────┐ ┌──────┐ ┌──────┐ │
|
||||
│ │ 🎵 │ │ 👤 │ │ 💿 │ │
|
||||
│ │Track│ │Artist│ │Album│ │
|
||||
│ └──────┘ └──────┘ └──────┘ │
|
||||
│ ┌──────┐ ┌──────┐ │
|
||||
│ │ 📝 │ │ 🎭 │ │
|
||||
│ │List │ │Genre│ │
|
||||
│ └──────┘ └──────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.2 Albums View Flow
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
AlbumsGrid[Albums Grid<br/>FORCED Grid View] --> UserAction{User Action}
|
||||
|
||||
UserAction -->|Click Album| AlbumDetail[Album Detail Page<br/>/library/[albumId]]
|
||||
UserAction -->|Click Play on Card| PlayAlbum[Play Album Immediately]
|
||||
|
||||
AlbumDetail --> ShowAlbum[Show Album:<br/>- Album Art<br/>- Title, Artist<br/>- Track List<br/>- Download Button<br/>- Favorite Button]
|
||||
|
||||
ShowAlbum --> TrackAction{User Action}
|
||||
|
||||
TrackAction -->|Click Track| PlayTrack[Play Track + Queue Album]
|
||||
TrackAction -->|Click Artist| NavArtist[Navigate to Artist Page]
|
||||
TrackAction -->|Download Album| DownloadFlow[Download Flow]
|
||||
TrackAction -->|Back Button| BackToGrid[Return to Albums Grid]
|
||||
```
|
||||
|
||||
**Album Detail Layout:**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ [←] [♡] [⬇] │
|
||||
│ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ Album Artwork │ │
|
||||
│ │ │ │
|
||||
│ └────────────────────┘ │
|
||||
│ │
|
||||
│ Album Title │
|
||||
│ Artist Name (clickable) │
|
||||
│ 2024 • 12 tracks • 45:23 │
|
||||
│ │
|
||||
│ [▶ Play] [🔀 Shuffle] │
|
||||
│ │
|
||||
│ ───────────────────────────────────── │
|
||||
│ 1 Track Title 3:45 │
|
||||
│ 2 Track Title 4:12 │
|
||||
│ 3 Track Title 3:28 │
|
||||
│ ... │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.3 Artist Navigation
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
ArtistsGrid[Artists Grid] --> ClickArtist[Click Artist]
|
||||
|
||||
ClickArtist --> ArtistPage[Artist Detail Page<br/>/library/artist/[id]]
|
||||
|
||||
ArtistPage --> ShowContent[Show Artist Content:<br/>- Artist Photo<br/>- Biography<br/>- Albums Grid<br/>- Top Tracks<br/>- Similar Artists]
|
||||
|
||||
ShowContent --> UserAction{User Action}
|
||||
|
||||
UserAction -->|Click Album| AlbumDetail[Album Detail Page]
|
||||
UserAction -->|Play Top Tracks| PlayArtist[Play Artist Radio]
|
||||
UserAction -->|Click Similar Artist| OtherArtist[Other Artist Page]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Search Flow
|
||||
|
||||
### 6.1 Search Page Navigation
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
BottomNav[Bottom Nav] --> ClickSearch[Click Search Tab]
|
||||
|
||||
ClickSearch --> SearchPage[Search Page<br/>/search]
|
||||
|
||||
SearchPage --> EmptyState{Has Query?}
|
||||
|
||||
EmptyState -->|No| ShowPrompt[Show Empty State:<br/>Search for music,<br/>movies, shows...]
|
||||
EmptyState -->|Yes| ShowResults[Show Results Grouped:<br/>- Songs<br/>- Albums<br/>- Artists<br/>- Movies<br/>- Episodes]
|
||||
|
||||
ShowPrompt --> UserTypes[User Types in Search]
|
||||
UserTypes --> LiveSearch[Live Search<br/>Debounced 300ms]
|
||||
LiveSearch --> ShowResults
|
||||
|
||||
ShowResults --> UserClick{User Clicks Result}
|
||||
|
||||
UserClick -->|Song| PlaySong[Play Song + Queue Results]
|
||||
UserClick -->|Album| NavAlbum[Navigate to Album Detail]
|
||||
UserClick -->|Artist| NavArtist[Navigate to Artist Page]
|
||||
UserClick -->|Movie| NavMovie[Navigate to Movie Detail]
|
||||
```
|
||||
|
||||
**Search Page Layout:**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ [🔍 Search...] [✕] │
|
||||
│ │
|
||||
│ Songs ──────────────────────────── │
|
||||
│ ♪ Song Title - Artist 3:45 │
|
||||
│ ♪ Song Title - Artist 4:12 │
|
||||
│ See all (23) │
|
||||
│ │
|
||||
│ Albums ─────────────────────────── │
|
||||
│ [Album Cover] Album Title │
|
||||
│ [Album Cover] Album Title │
|
||||
│ See all (8) │
|
||||
│ │
|
||||
│ Artists ────────────────────────── │
|
||||
│ [Photo] Artist Name │
|
||||
│ See all (5) │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Download Flows
|
||||
|
||||
### 7.1 Initiating Downloads
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
User[User on Album/Track Page] --> ClickDownload[Click Download Button]
|
||||
|
||||
ClickDownload --> CheckType{Download Type?}
|
||||
|
||||
CheckType -->|Single Track| DownloadTrack[Download Single File]
|
||||
CheckType -->|Album| DownloadAlbum[Download All Tracks]
|
||||
CheckType -->|Artist| ShowOptions[Show Options Dialog]
|
||||
|
||||
ShowOptions --> UserChoice{User Choice}
|
||||
UserChoice -->|Discography| DownloadAll[Download All Albums]
|
||||
UserChoice -->|Select Albums| AlbumPicker[Album Selection UI]
|
||||
|
||||
DownloadTrack --> QueueDownload[Queue in Download Manager]
|
||||
DownloadAlbum --> QueueMultiple[Queue Multiple Files]
|
||||
|
||||
QueueDownload --> ShowProgress[Show Progress Ring<br/>on Download Button]
|
||||
QueueMultiple --> ShowProgress
|
||||
|
||||
ShowProgress --> DownloadActive[Download Active:<br/>Button shows % complete]
|
||||
```
|
||||
|
||||
**Download Button States:**
|
||||
```
|
||||
States:
|
||||
1. [⬇] Available - Gray outline
|
||||
2. [○ 45%] Downloading - Blue ring progress
|
||||
3. [✓] Downloaded - Green checkmark
|
||||
4. [!] Failed - Red with retry option
|
||||
5. [⏸] Paused - Yellow pause icon
|
||||
```
|
||||
|
||||
### 7.2 Managing Downloads Page
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
User[User] --> NavChoice{Navigation Path}
|
||||
|
||||
NavChoice -->|Desktop| HeaderNav[Header: Click Downloads Link]
|
||||
NavChoice -->|Mobile| HeaderIcon[Header: Click Downloads Icon]
|
||||
NavChoice -->|Direct| TypeURL[Type /downloads]
|
||||
|
||||
HeaderNav --> DownloadsPage[Downloads Page<br/>/downloads]
|
||||
HeaderIcon --> DownloadsPage
|
||||
TypeURL --> DownloadsPage
|
||||
|
||||
DownloadsPage --> ShowTabs[Show Tabs:<br/>Active | Completed]
|
||||
|
||||
ShowTabs --> ActiveTab{Active Tab}
|
||||
|
||||
ActiveTab -->|Active| ShowActive[Show Active Downloads:<br/>- Download progress bars<br/>- Pause/Resume buttons<br/>- Cancel buttons]
|
||||
ActiveTab -->|Completed| ShowCompleted[Show Completed:<br/>- Downloaded items list<br/>- Delete buttons<br/>- Play buttons]
|
||||
|
||||
ShowActive --> UserAction1{User Action}
|
||||
UserAction1 -->|Pause| PauseDownload[Pause Download]
|
||||
UserAction1 -->|Cancel| CancelDialog[Show Confirm Dialog]
|
||||
|
||||
ShowCompleted --> UserAction2{User Action}
|
||||
UserAction2 -->|Play| PlayOffline[Play from Local File]
|
||||
UserAction2 -->|Delete| DeleteDialog[Show Confirm Dialog]
|
||||
```
|
||||
|
||||
**Navigation to Downloads:**
|
||||
- **Desktop:** Click "Downloads" link in header navigation
|
||||
- **All screen sizes:** Click download icon (⬇) button in header user menu
|
||||
- **Direct:** Navigate to `/downloads` route
|
||||
|
||||
**Downloads Page Layout:**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ [←] Downloads │
|
||||
│ │
|
||||
│ [Active (3)] [Completed (12)] │
|
||||
│ │
|
||||
│ ─ Downloading ──────────────────── │
|
||||
│ │
|
||||
│ Album Cover Album Title │
|
||||
│ Artist Name │
|
||||
│ [████████░░] 80% │
|
||||
│ [⏸ Pause] [✕ Cancel] │
|
||||
│ │
|
||||
│ Album Cover Album Title │
|
||||
│ Artist Name │
|
||||
│ [██░░░░░░░░] 20% │
|
||||
│ [⏸ Pause] [✕ Cancel] │
|
||||
│ │
|
||||
│ ─ Queued ───────────────────────── │
|
||||
│ │
|
||||
│ Album Cover Album Title │
|
||||
│ Artist Name │
|
||||
│ Waiting... │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Settings & Account Flows
|
||||
|
||||
### 8.1 Settings Navigation
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
User[User] --> NavChoice{Navigation Path}
|
||||
|
||||
NavChoice -->|Desktop| HeaderSettings[Header: Click Settings Link]
|
||||
NavChoice -->|Mobile| OverflowMenu[Click Overflow Menu<br/>→ Settings]
|
||||
NavChoice -->|Direct| TypeURL[Navigate to /settings]
|
||||
|
||||
HeaderSettings --> SettingsPage[Settings Page<br/>/settings]
|
||||
OverflowMenu --> SettingsPage
|
||||
TypeURL --> SettingsPage
|
||||
|
||||
SettingsPage --> ShowSections[Show Sections:<br/>- Account<br/>- Playback<br/>- Downloads<br/>- Appearance<br/>- About]
|
||||
|
||||
ShowSections --> UserClick{User Clicks Section}
|
||||
|
||||
UserClick -->|Account| AccountSettings[Account Settings:<br/>- Server URL<br/>- Username<br/>- Logout button]
|
||||
UserClick -->|Playback| PlaybackSettings[Playback Settings:<br/>- Gapless playback<br/>- Volume normalization<br/>- Crossfade duration]
|
||||
UserClick -->|Downloads| DownloadSettings[Download Settings:<br/>- Max concurrent<br/>- WiFi only<br/>- Storage location<br/>- Auto-cache next tracks]
|
||||
UserClick -->|Appearance| AppearanceSettings[Appearance Settings:<br/>- Dark mode<br/>- Accent color]
|
||||
```
|
||||
|
||||
**Navigation to Settings:**
|
||||
- **Desktop:** Click "Settings" link in header navigation
|
||||
- **Mobile:** Click three-dot overflow menu → Select "Settings"
|
||||
- **Direct:** Navigate to `/settings` route
|
||||
|
||||
### 8.2 Logout Flow
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
AnyScreen[Any Screen] --> ClickLogout[Click Logout Button<br/>in Header]
|
||||
|
||||
ClickLogout --> ConfirmDialog[Show Confirmation:<br/>"Log out of [Server]?"]
|
||||
|
||||
ConfirmDialog --> UserConfirm{User Confirms?}
|
||||
|
||||
UserConfirm -->|No| CancelLogout[Cancel - Stay on Current Screen]
|
||||
UserConfirm -->|Yes| StopPlayer[Stop Playback]
|
||||
|
||||
StopPlayer --> ClearToken[Delete Token from Keyring]
|
||||
ClearToken --> ClearState[Clear App State:<br/>- Player state<br/>- Queue<br/>- Current screen]
|
||||
|
||||
ClearState --> NavLogin[Navigate to Login Screen<br/>/login]
|
||||
|
||||
NavLogin --> ShowLogin[Show Login Screen:<br/>- No Header<br/>- No Bottom Nav<br/>- No MiniPlayer]
|
||||
```
|
||||
|
||||
**Logout Button Location:**
|
||||
- Always visible in header user menu (logout icon)
|
||||
- Accessible from any authenticated screen
|
||||
|
||||
---
|
||||
|
||||
## 9. Background & Lock Screen Behavior
|
||||
|
||||
### 9.1 Audio Playback in Background (Android)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Playing[Audio Playing] --> Background{User Action}
|
||||
|
||||
Background -->|Home Button| AppBackground[App to Background]
|
||||
Background -->|Screen Lock| ScreenLock[Screen Locked]
|
||||
|
||||
AppBackground --> ContinuePlay[Playback Continues]
|
||||
ScreenLock --> ContinuePlay
|
||||
|
||||
ContinuePlay --> ShowNotification[Show Media Notification:<br/>- Artwork<br/>- Title/Artist<br/>- Play/Pause<br/>- Next/Previous]
|
||||
|
||||
ShowNotification --> LockScreen[Lock Screen Controls:<br/>Media Session Integration]
|
||||
|
||||
LockScreen --> UserInteract{User Interaction}
|
||||
|
||||
UserInteract -->|Tap Notification| OpenApp[Open App to Last Screen<br/>with MiniPlayer]
|
||||
UserInteract -->|Lock Screen Controls| SendCommand[Send Command to Player]
|
||||
UserInteract -->|BLE Headset Button| HeadsetControl[AVRCP Command]
|
||||
```
|
||||
|
||||
**Notification Layout (Android):**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ [Artwork] Song Title │
|
||||
│ Artist Name │
|
||||
│ Album Name │
|
||||
│ │
|
||||
│ [⏮] [⏸] [⏭] [✕] │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 9.2 Video Playback in Background
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
VideoPlaying[Video Playing] --> Background{User Action}
|
||||
|
||||
Background -->|Home Button| AutoPause[Automatically Pause]
|
||||
Background -->|Screen Lock| AutoPause
|
||||
|
||||
AutoPause --> SaveProgress[Save Progress]
|
||||
SaveProgress --> ShowNotification[Show Paused Notification:<br/>"Tap to Resume"]
|
||||
|
||||
ShowNotification --> UserReturn{User Returns?}
|
||||
|
||||
UserReturn -->|Tap Notification| ResumeVideo[Open App to Video Player]
|
||||
UserReturn -->|Later| KeepPaused[Video Remains Paused]
|
||||
|
||||
ResumeVideo --> AskResume[Resume from Saved Position]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Error States & Edge Cases
|
||||
|
||||
### 10.1 Network Loss During Streaming
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Streaming[Streaming Audio/Video] --> LoseNetwork[Network Connection Lost]
|
||||
|
||||
LoseNetwork --> CheckLocal{Local Copy<br/>Available?}
|
||||
|
||||
CheckLocal -->|Yes| SwitchLocal[Switch to Local Playback<br/>Seamlessly]
|
||||
CheckLocal -->|No| ShowBuffer[Show Buffering Spinner]
|
||||
|
||||
ShowBuffer --> WaitReconnect[Wait for Reconnection<br/>30 second timeout]
|
||||
|
||||
WaitReconnect --> Reconnect{Reconnected?}
|
||||
|
||||
Reconnect -->|Yes| Resume[Resume Streaming]
|
||||
Reconnect -->|No| ShowError[Show Error Toast:<br/>"Unable to stream.<br/>Check connection."]
|
||||
|
||||
ShowError --> OfferRetry[Offer Retry Button]
|
||||
ShowError --> OfferDownload[Offer "Download for Offline"]
|
||||
```
|
||||
|
||||
### 10.2 Server Unreachable
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Action[User Action Requires Server] --> TryConnect[Attempt Connection]
|
||||
|
||||
TryConnect --> Timeout{Connection<br/>Timeout?}
|
||||
|
||||
Timeout -->|Yes| ShowError[Show Error:<br/>"Server unreachable"]
|
||||
Timeout -->|No| Success[Action Succeeds]
|
||||
|
||||
ShowError --> OfferOptions[Offer Options:<br/>- Retry<br/>- Switch to Offline Mode<br/>- Change Server]
|
||||
```
|
||||
|
||||
### 10.3 Download Failed
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Downloading[Download in Progress] --> Failure{Failure Type?}
|
||||
|
||||
Failure -->|Network Error| Retry[Auto-retry<br/>with Backoff]
|
||||
Failure -->|Disk Full| ShowDiskError[Show Error:<br/>"Not enough storage"]
|
||||
Failure -->|Server Error| ShowServerError[Show Error:<br/>"Server error"]
|
||||
|
||||
Retry --> RetryCount{Retry Count<br/>< 3?}
|
||||
RetryCount -->|Yes| Downloading
|
||||
RetryCount -->|No| Failed[Mark as Failed]
|
||||
|
||||
ShowDiskError --> Failed
|
||||
ShowServerError --> Failed
|
||||
|
||||
Failed --> UserAction[Show in Downloads:<br/>with Retry Button]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Platform-Specific UX Patterns
|
||||
|
||||
### 11.1 Android-Specific
|
||||
|
||||
**Hardware Back Button:**
|
||||
- **In Full Player:** Return to previous screen, show MiniPlayer
|
||||
- **In Video Player:** Stop playback, exit fullscreen
|
||||
- **In Album Detail:** Return to library grid
|
||||
- **At Library Home:** Exit app (show confirmation)
|
||||
|
||||
**System Volume Buttons:**
|
||||
- **While playing audio:** Adjust playback volume
|
||||
- **While controlling remote session:** Adjust remote session volume (shows session name in volume panel)
|
||||
- **In menus:** Adjust system volume (default behavior)
|
||||
|
||||
**Share Integration:**
|
||||
- Long-press album/song → Share menu
|
||||
- Options: Share with other apps, Copy link
|
||||
|
||||
### 11.2 Linux Desktop-Specific
|
||||
|
||||
**Keyboard Shortcuts:**
|
||||
- `Space`: Play/Pause
|
||||
- `→`: Next track
|
||||
- `←`: Previous track
|
||||
- `/`: Focus search
|
||||
- `Ctrl+Q`: Quit
|
||||
|
||||
**Window Behavior:**
|
||||
- Minimize to tray (playback continues)
|
||||
- Close window (show confirmation if playing)
|
||||
- MPRIS integration for desktop media controls
|
||||
|
||||
**Mouse Interactions:**
|
||||
- Hover over MiniPlayer: Show additional controls (volume, queue peek)
|
||||
- Right-click: Context menu (Add to playlist, Go to artist, Download)
|
||||
|
||||
---
|
||||
|
||||
## 12. UX Principles Summary
|
||||
|
||||
### 12.1 Core Principles
|
||||
|
||||
1. **Playback Persistence:**
|
||||
- Audio playback never stops unless user explicitly stops it
|
||||
- MiniPlayer visible on all screens (except video/login)
|
||||
- Queue and position preserved across navigation
|
||||
|
||||
2. **Non-Blocking UI:**
|
||||
- Downloads happen in background
|
||||
- Sync operations never block user interaction
|
||||
- Optimistic updates (favorite, progress) with background sync
|
||||
|
||||
3. **Offline-First:**
|
||||
- Downloaded content works offline
|
||||
- Seamless switch between online/offline
|
||||
- Progress and preferences saved locally
|
||||
|
||||
4. **Progressive Disclosure:**
|
||||
- Simple defaults, advanced options hidden
|
||||
- Context menus for secondary actions
|
||||
- Settings organized by category
|
||||
|
||||
5. **Responsive Design:**
|
||||
- Mobile-first UI
|
||||
- Desktop enhancements (hover states, keyboard shortcuts)
|
||||
- Tablet: Grid layouts with more columns
|
||||
|
||||
### 12.2 Animation & Transitions
|
||||
|
||||
| Transition | Duration | Easing |
|
||||
|------------|----------|--------|
|
||||
| MiniPlayer slide up/down | 300ms | ease-out |
|
||||
| Screen navigation | 200ms | ease-in-out |
|
||||
| Video controls fade | 500ms | ease-out |
|
||||
| Download button state change | 150ms | ease-in-out |
|
||||
| Modal appear | 200ms | ease-out |
|
||||
| Toast notification | 250ms | ease-in-out |
|
||||
|
||||
### 12.3 Touch Targets (Mobile)
|
||||
|
||||
| Element | Minimum Size |
|
||||
|---------|--------------|
|
||||
| Bottom nav buttons | 48x48 dp |
|
||||
| List item (track, album) | Full width x 56 dp |
|
||||
| Player controls | 56x56 dp |
|
||||
| MiniPlayer | Full width x 64 dp |
|
||||
| Download button | 40x40 dp |
|
||||
| Favorite button | 40x40 dp |
|
||||
|
||||
---
|
||||
|
||||
## 13. Future UX Enhancements
|
||||
|
||||
### 13.1 Planned Features
|
||||
|
||||
1. **Gesture Navigation:**
|
||||
- Swipe up on MiniPlayer → Full player
|
||||
- Swipe down on full player → Back to previous screen
|
||||
- Swipe between tracks in full player
|
||||
|
||||
2. **Queue Management UI (DR-020):**
|
||||
- Drag to reorder
|
||||
- Swipe to remove
|
||||
- Add to queue vs. Play next
|
||||
|
||||
3. **Sleep Timer (UR-026):**
|
||||
- Accessible from full player menu
|
||||
- Presets: 15min, 30min, 1hr, End of track, End of album
|
||||
- Countdown visible in MiniPlayer
|
||||
|
||||
4. **Home Screen (UR-034):**
|
||||
- Hero banner carousel
|
||||
- Continue watching/listening
|
||||
- Recently added
|
||||
- Personalized recommendations
|
||||
|
||||
5. **Cast/Remote Control Enhancements:**
|
||||
- Picture-in-picture for remote sessions
|
||||
- Multi-room audio (play on multiple devices)
|
||||
- Handoff (transfer playback to phone from TV)
|
||||
|
||||
### 13.2 Accessibility Enhancements
|
||||
|
||||
- Screen reader optimization
|
||||
- High contrast mode
|
||||
- Larger text option
|
||||
- Voice control integration
|
||||
- Haptic feedback for controls
|
||||
|
||||
---
|
||||
|
||||
This UX flow documentation should be updated as new features are implemented and user feedback is incorporated.
|
||||
Reference in New Issue
Block a user