Skip to content

Console client build guide

Developer build spec for the Phlix terminal (TUI) client — PHP 8.3+, SugarCraft stack. For end-user install and setup, see Console Client.

Table of Contents

  1. Overview
  2. Development Environment
  3. Project Structure
  4. Architecture
  5. API Client Implementation
  6. Rendering Pipeline
  7. Screen System
  8. Testing
  9. Building a PHAR
  10. Implementation Checklist

1. Overview

1.1 Platform Capabilities

The console client renders in any modern terminal using:

  • Graphics protocols: sixel, kitty, iTerm2 — real image rendering
  • Cell modes: half-block, quarter-block, ascii, ansi256, truecolor — character-based fallback
  • ANSI escape codes: cursor control, colors, position

1.2 Key Libraries

LibraryPurpose
sugarcraft/sugarCore TUI framework
sugarcraft/inputKeyboard/raw input handling
`sugarcraft mosaicMosaic grid layout
sugarcraft/posterPoster image rendering
sugarcraft/videoVideo frame decoding

2. Development Environment

2.1 Required Software

1. PHP 8.3+ (CLI)
2. Composer 2.x
3. Git
4. ffmpeg + ffprobe (video decode)
5. ffplay or mpv (audio playback)
6. A terminal emulator with graphics protocol support:
   - iTerm2 (macOS) — native sixel/iTerm2 support
   - Kitty — native kitty protocol
   - WezTerm — sixel support
   - Windows Terminal — sixel support (Windows 11 22H2+)

2.2 Project Setup Commands

bash
# Clone the repository
git clone https://github.com/detain/phlix-console-client.git
cd phlix-console-client

# Install dependencies
composer install

# Verify installation
./bin/phlix doctor

3. Project Structure

phlix-console-client/
├── bin/
│   └── phlix                 # CLI entrypoint
├── src/
│   ├── Api/
│   │   ├── ApiClient.php    # Base API client
│   │   └── Admin/
│   │       └── AdminClient.php  # Admin API endpoints
│   ├── Config/
│   │   ├── Config.php       # Application configuration
│   │   ├── TokenStore.php   # Auth token persistence
│   │   ├── ServerEntry.php  # Server configuration entry
│   │   └── TokenBundle.php  # Auth token bundle
│   ├── Console/
│   │   ├── App.php          # Main application
│   │   ├── Capabilities.php # Terminal capability detection
│   │   └── ColorPalette.php # ANSI color handling
│   ├── Media/
│   │   ├── PosterLoader.php  # Poster image loading
│   │   └── MosaicFactory.php # Mosaic grid factory
│   ├── Screen/
│   │   ├── Screen.php       # Base screen interface
│   │   ├── HomeScreen.php   # Home/library rail screen
│   │   ├── GridScreen.php   # Virtualized poster grid
│   │   ├── DetailScreen.php # Media detail view
│   │   ├── PlayerScreen.php # Video player
│   │   └── AdminUsersScreen.php # Admin user management
│   ├── Store/
│   │   ├── AuthStore.php    # Authentication state
│   │   ├── LibrariesStore.php # Libraries cache
│   │   └── MediaStore.php   # Media items cache
│   └── Msg/
│       ├── Msg.php          # Base message
│       ├── NavigateBackMsg.php
│       ├── OpenLibraryMsg.php
│       ├── OpenDetailMsg.php
│       └── ...              # Other messages
├── tests/
│   ├── Unit/
│   └── Integration/
├── docs/
│   └── images/              # Render mode screenshots
├── composer.json
├── phpstan.neon
└── README.md

4. Architecture

4.1 Application Flow

bin/phlix
  └── buildContainer()    — Build DI container
        └── App::boot()   — Bootstrap application
              └── Program::run() — Main event loop

Event Loop:
  Input → Dispatch → Screen → Render → Output

4.2 Screen System

Screens implement Screen interface and use ThemedScreen trait:

php
interface Screen
{
    public function render(): string;           // Render the screen
    public function handle(Msg $msg): ?Screen;  // Handle a message, return next screen
    public function columns(): int;             // Terminal columns
    public function rows(): int;               // Terminal rows
}

The App hand-rolls its own routing rather than using candy-core's ScreenStack. The ScreenStack discards a nested screen's updated model when navigating away, so it cannot host a stateful form — the Route enum (see src/Route.php:13-15) names the current destination instead:

php
/**
 * The top-level screen the {@see App} is showing. The App hand-rolls routing
 * (candy-core's ScreenStack discards a nested screen's updated model, so it
 * can't host a stateful form) — this enum names the current destination.
 */
enum Route { ... }

4.3 Message Passing

Screens communicate via immutable messages:

php
// Navigate to a library
$navigateMsg = new OpenLibraryMsg($libraryId);

// Open media detail
$detailMsg = new OpenDetailMsg($mediaId);

// Go back
$backMsg = new NavigateBackMsg();

5. API Client Implementation

5.1 ApiClient Class

php
// src/Api/ApiClient.php

class ApiClient
{
    public function __construct(string $serverUrl)
    {
        $this->baseUrl = rtrim($serverUrl, '/');
        $this->token = TokenStore::default()->load()?->accessToken;
    }

    public function request(string $method, string $path, array $body = null): mixed
    {
        $url = $this->baseUrl . '/api/v1' . $path;
        $headers = ['Content-Type: application/json'];

        if ($this->token !== null) {
            $headers[] = 'Authorization: Bearer ' . $this->token;
        }

        // ... HTTP request via cURL
    }

    // Authentication
    public function login(string $username, string $password): array;
    public function logout(): void;

    // Libraries
    public function getLibraries(): array;
    public function getLibraryItems(string $libraryId, array $options = []): array;

    // Media
    public function getItem(string $itemId): array;
    public function getPlaybackInfo(string $itemId): array;
    public function getPoster(string $itemId, int $width = 400): string;

    // User data
    public function updateUserData(string $itemId, array $data): void;
    public function getProgress(string $itemId): ?array;
    public function saveProgress(string $itemId, array $progress): void;

    // Search
    public function search(string $query, array $options = []): array;
}

6. Rendering Pipeline

6.1 Mosaic Grid

Posters are rendered in a mosaic grid using MosaicFactory:

php
// Create a mosaic for the poster grid
$mosaic = MosaicFactory::forPosterGrid($mode); // $mode = 'sixel', 'halfblock', etc.

// Render a single poster
$poster = $posterLoader->load($mediaItem, $width, $height);

// Render the grid
$grid = $mosaic->renderGrid($posters, $columns, $rows);

6.2 Render Modes

php
// Cell modes (tile as colored text)
halfblock 24-bit color half-block characters ()
quarterblock dense color blocks (▘▝▖▗)
ascii monochrome character ramp
ansi256 256-color character ramp
truecolor 24-bit color character ramp

// Graphics modes (real images tiled)
sixel Sixel protocol
kitty Kitty protocol
iterm2 iTerm2 inline images
auto Half-block by default, graphics on demand

6.3 Video Frame Extraction

Video frames are decoded via ffmpeg and converted to ANSI-escaped strings:

php
// Probe a video file using ffprobe
$report = $this->videoSpike->probeReport($path);
// Returns: duration, resolution, codecs, etc.

// Extract frames using ffmpeg
$frames = $this->videoSpike->frames($path, $count = 1, $cols = 60, $rows = 20, $mode = 'halfblock');
// Yields ANSI-escaped strings for each frame

7. Screen System

7.1 Home Screen

The home screen displays library rails:

┌─ Phlix ─────────────────────────────────────────────┐
│                                                     │
│  Movies   TV Shows   Music   Audiobooks   Photos   │
│  ████████████████████████████████████████           │
│  [poster] [poster] [poster] [poster] [poster]       │
│                                                     │
│  [poster] [poster] [poster] [poster] [poster]       │
│                                                     │
└─────────────────────────────────────────────────────┘

7.2 Grid Screen

Virtualized poster grid with filtering and sorting:

  • Scroll handling (↑↓←→, Page Up/Down, Home/End)
  • A–Z jump bar
  • Filter by genre, year, etc.
  • Sort by name, date added, rating

7.3 Detail Screen

Media detail with:

  • Large hero poster
  • Metadata (title, year, rating, duration, synopsis)
  • Cast list with ANSI avatars
  • Play button and action menu

7.4 Player Screen

In-terminal video player:

  • Direct play through ffmpeg (HEVC/MKV/AV1 support)
  • Scrubber with chapter ticks
  • Resume from last position
  • Progress reporting to server
  • Up-next auto-advance
  • On-demand subtitles
  • Transcode fallback

8. Testing

bash
# Run all tests
composer test

# Run unit tests only
vendor/bin/phpunit --testsuite Unit

# Run with coverage
vendor/bin/phpunit --coverage-html coverage-report/

# Static analysis
composer phpstan

9. Building a PHAR

bash
# Install box PHAR builder
composer require --dev humbug/box

# Build
./scripts/build-phar.sh

# Output: build/phlix.phar

The PHAR is a self-contained executable containing all dependencies.


10. Implementation Checklist

  • [x] C0: Project bootstrap, composer.json, autoloader
  • [x] C1: Config/TokenStore, Config::dir(), Config::load()
  • [x] C2: ApiClient skeleton + GET /libraries
  • [x] C3: HomeScreen with library rails
  • [x] C4: GridScreen (virtualized poster grid)
  • [x] C5: DetailScreen (hero, metadata, cast)
  • [x] C6: PlayerScreen (ffmpeg direct play, scrubber, resume)
  • [x] C7: Search, Command Palette, Toasts
  • [x] C8: Admin sections (Dashboard, Users, Plugins, etc.)
  • [x] C9: Music/Audiobook/Photo/Book browsers
  • [x] C10: Polish, theming, Now-Playing bar, metrics HUD

BSD-3-Clause