Stability changes have been updated.

This commit is contained in:
2026-04-05 13:11:18 -04:00
parent b0a190f592
commit ed71d771ea
18 changed files with 410 additions and 301 deletions

View File

@@ -2,138 +2,100 @@
All notable changes to this project will be documented in this file.
## [0.3.0-rc.3] - 2026-04-04
### Release Engineering
- Added Windows-specific contributor scripts for the core `just install-w`, `just dev`, and `just check` path, while keeping the broader Unix-oriented utility and release recipes unchanged for now.
- Updated the release checklist and contributor docs to call out the supported Windows RC.2 workflow and the manual Windows verification follow-up required before promotion.
### Regression Coverage
- Added a startup regression test for the RC.1 security fix: an invalid config on a configured instance with existing users no longer re-enables unauthenticated setup-only access.
- Expanded Playwright stabilization coverage for retry countdown rendering, Library & Intake manual scan success/failure surfacing, and completed job detail rendering from persisted encode stats.
## [0.3.0-rc.1] - 2026-04-04
## [0.3.0] - 2026-04-05
### Security
- Fixed critical bug where a config parse failure on a configured instance would re-enable unauthenticated setup endpoints (filesystem browse, settings bundle) for any network client.
- Fixed session cookies being marked Secure by default in release builds, breaking login over plain HTTP/LAN. Secure is now opt-in via ALCHEMIST_COOKIE_SECURE=true for reverse-proxy deployments.
- Restricted /api/fs/* filesystem browsing to loopback connections only during the initial setup flow.
### Backend — Engine & Pipeline
- Boot auto-analysis: server scans the library and runs ffprobe on all queued jobs at startup before the user clicks Start, so skip/transcode decisions are pre-computed.
- File watcher triggers automatic analysis after each new file is enqueued, using a coalesced AtomicBool to avoid redundant passes on burst arrivals.
- Watcher-triggered analysis now uses try_acquire on the analysis semaphore — if a pass is already running, new triggers are dropped rather than queuing up.
- Boot analysis uses a separate blocking-acquire path (analyze_pending_jobs_boot) to guarantee it runs to completion before the engine starts.
- Fixed infinite analysis loop: the batch query now excludes jobs that already have a decision row, preventing transcodable jobs from being re-analyzed on every loop iteration.
- Removed reset_interrupted_jobs from the per-pass analysis loop — it is now only called once at startup as a recovery tool.
- Engine no longer auto-pauses when the queue empties. It stays Running and picks up new files automatically as the watcher delivers them.
- Idle state: frontend derives ● Idle from engine_status=running + active_jobs=0, shown with a Stop button still visible.
- Added Drop guard for in_flight_jobs counter so it decrements correctly even if process_job panics.
- Completed job detail no longer re-runs ffprobe on the input file; encode_stats table is the source of truth for size, codec, and timing data.
- Boot analysis batches jobs in groups of 100 from offset 0 rather than paginating with an advancing offset, fixing a bug where transcodable jobs shifted out of later pages after earlier jobs were decided.
- Startup cleanup now covers cancelled jobs and .alchemist-part subtitle sidecar temp files in addition to interrupted encoding jobs.
- Ctrl+C / SIGTERM now exits the process cleanly after graceful shutdown completes. Background tasks (run loop, scheduler, watcher, maintenance) no longer prevent process exit.
### Backend — Hardware & Encoding
- VideoToolbox encode commands now include -allow_sw 1 (software fallback when GPU is busy) and a format=yuv420p filter (required pixel format), fixing all VideoToolbox encodes on macOS.
- HEVC VideoToolbox output correctly tagged as hvc1 for broad Apple device compatibility.
- Audio heavy-codec detection no longer uses a 640 kbps bitrate threshold — standard eac3/Atmos at 768+ kbps now copies through without transcoding. Only lossless codecs (TrueHD, MLP, DTS-HD, FLAC, PCM) trigger audio transcoding.
- Audio transcoding for MKV containers now checks for libopus availability at runtime and falls back to AAC when libopus is not compiled into the FFmpeg binary (common on macOS).
- FFmpeg encode failures now write the full error message (including last 20 lines of FFmpeg stderr) to the job log table so job_failure_summary surfaces actionable detail in the UI.
- VideoToolbox-specific error patterns added to the UI failure explainer (vt_compression, mediaserverd, no capable devices, etc.).
- Analysis semaphore (Semaphore(1)) serializes all analysis passes, preventing concurrent ffprobe runs from racing on job state.
### Backend — Database & API
- get_jobs_for_analysis_batch excludes jobs with existing decision rows via NOT EXISTS subquery, preventing infinite re-analysis of transcodable jobs.
- OOM protection: get_jobs_for_analysis_batch uses LIMIT/OFFSET pagination so large libraries do not load all jobs into memory at once.
- get_duplicate_candidates uses a SQL subquery to filter to stems that actually appear more than once before fetching rows, avoiding full-library fetch.
- Manual scan (Scan Now button) now propagates errors to the caller instead of always returning 200, and triggers analyze_pending_jobs after completion, matching boot and setup scan behaviour.
- Index added on decisions(job_id, created_at) covering the NOT EXISTS analysis batch query.
### UI & Frontend
- Setup wizard welcome step (step 0): Alchemist logo, tagline, and a single Get Started button before the admin account form.
- Analyzing job rows now show an indeterminate shimmer animation instead of a static 0.0% label.
- Retry countdown on failed job rows: "Retrying in 47m" updates every 30 seconds based on attempt count and updated_at timestamp.
- Poll-based job state updates no longer overwrite terminal states that arrived via SSE. When the server confirms a job is no longer terminal (e.g. after retry), the server state wins.
- Statistics page uses recharts AreaChart for savings over time and BarChart for codec breakdown, replacing custom CSS bars that failed to render in flex containers.
- Setup wizard file browser fixed: panel height changed from calc(100dvh-20rem) to a fixed 420px, preventing the panel from collapsing to zero height inside the double-scroll setup shell.
- Breadcrumb crash in the setup wizard fixed: frontend interface now correctly uses label (not name) to match the backend FsBreadcrumb field name.
- Jobs toolbar floats directly on the page background — removed the border/card wrapper.
- Hardware settings merged into the Transcoding tab.
- Notifications and Automation merged into a single tab.
- React errors #418 and #423 fixed: activeIndex effect removed from SettingsPanel; applyRootTheme deferred into requestAnimationFrame in AppearanceSettings.
- Mobile layout: hamburger sidebar overlay, jobs table hides date/priority columns below md breakpoint, stat cards use 2×2 grid on small screens.
- Sidebar hidden on mobile with hamburger menu; overlay sidebar with backdrop and close button.
- e2e reliability tests added to just check-web so UI regressions are caught in CI before merge.
## [v0.2.10-rc.5] - 2026-03-22
### Runtime & Queue Control
- Engine runtime modes now support `background`, `balanced`, and `throughput`, with manual concurrency overrides and drain/resume controls exposed through the API and dashboard header.
- Engine status reporting now includes pause source, drain state, concurrent-limit metadata, and mode visibility for troubleshooting.
- Queue management continued to harden with safer active-job controls, clearer failure surfacing, and better per-job operational feedback.
### Media Processing & Library Health
- VAAPI-first Intel handling, remux planning, subtitle sidecars, and library health issue reporting were expanded across the planner, FFmpeg integration, and dashboard.
- Hardware detection and probe logging were improved to make CPU/GPU backend selection and diagnostics easier to understand.
- Stream rules landed for commentary stripping, audio-language filtering, and keeping only the default audio track when needed.
### Paths, Setup & Docs
- Default config and database paths were normalized around the `alchemist` runtime home, and the repo now ships a `justfile` for common dev, release, Docker, and database workflows.
- The docs site moved onto Starlight and now builds locally with a proper content config, localized collection wiring, and a corrected splash-page schema.
- Release automation now bumps repo-wide version manifests, supports checkpoint commits before release validation, and isolates web-e2e onto a separate port so a local server on `3000` does not block release verification.
### CI/CD & Release Tooling
- Docker publishing is now gated behind successful validation, and local release verification covers Rust checks/tests, frontend verification, docs build, `actionlint`, and the reliability Playwright suite.
- `just update` does not create the release commit, git tag, or push until the full validation gate passes.
## [v0.2.10-rc.2] - 2026-03-21
### Stability & Reliability
- VMAF quality gating: encodes falling below a configurable minimum score are now rejected rather than silently promoted.
- Exponential retry backoff for failed jobs: 5 / 15 / 60 / 360 minute delays based on attempt count prevent tight failure loops.
- Orphaned temp file cleanup on startup: interrupted encodes no longer leave `.alchemist.tmp` files on disk indefinitely.
- Log table pruning: configurable retention period (default 30 days) prevents unbounded log growth on busy servers.
- Auth session cleanup: expired sessions are pruned on startup and every 24 hours.
- Resource endpoint caching: `/api/system/resources` is cached for 500ms to prevent redundant OS probes from multiple open browser tabs.
- Fixed a critical bug where a config parse failure on a configured instance would re-enable unauthenticated setup endpoints (filesystem browse, settings bundle) for any network client.
- Session cookies are no longer marked `Secure` by default, which was breaking login over plain HTTP/LAN. Opt in with `ALCHEMIST_COOKIE_SECURE=true` for reverse-proxy deployments.
- `/api/fs/*` filesystem browsing is now restricted to loopback connections only during the initial setup flow.
- Proxy header handling hardened with explicit trust configuration for reverse-proxy deployments.
### New Features
- Per-library profiles: each watch folder can have its own transcoding profile, with four built-in presets (Space Saver, Quality First, Balanced, Streaming) usable as starting points.
- Storage savings dashboard: the Stats page now shows total space recovered, average reduction percentage, a savings-over-time chart, and per-codec breakdowns.
- Library Doctor: scan your library for corrupt or broken files directly from System Settings.
- `/api/jobs` added as a canonical alias for `/api/jobs/table`.
### Beginner Experience
- Plain-English skip reasons: skipped jobs now show a human-readable explanation with technical detail available in an expandable section.
- Auto-redirect to the setup wizard for first-time users with no watch directories configured.
- Setup wizard field descriptions: CRF, BPP, concurrent jobs, and CPU preset now include plain-English explanations inline.
- Telemetry is now opt-in by default, with a detailed explanation of exactly what is collected.
#### Library & Encoding
- **Per-library profiles** — each watch folder gets its own transcoding profile. Four built-in presets (Space Saver, Quality First, Balanced, Streaming) are ready to use or customize.
- **Container remuxing** — files already in the target codec but wrapped in MP4/MOV are remuxed to MKV losslessly, skipping a full re-encode.
- **Subtitle sidecar extraction** — text-based subtitle tracks (SRT, ASS, VTT) can be extracted as separate files alongside the output rather than muxed in.
- **Stream rules** — strip audio tracks by title keyword (e.g. commentary tracks), filter by language code, or keep only the default audio track.
- **VMAF quality gating** — encodes scoring below a configurable threshold are rejected and the source is preserved.
- **Library Intelligence** — duplicate detection surfaces files with matching stems across the library.
- **Library Doctor** — health scanning detects corrupt or broken files directly from System Settings.
- **Mirrored output root** — write transcoded files to a separate directory tree that mirrors the source structure, rather than alongside the source.
### Job Management
- Skipped tab: dedicated tab in the Job Manager for skipped jobs.
- Archived tab: cleared completed jobs are now visible in an Archived tab rather than disappearing permanently.
- Sort controls: the job list can now be sorted by last updated, date added, file name, or file size.
#### Job Management
- **Skipped tab** — dedicated tab for skipped jobs with structured skip reasons.
- **Archived tab** — cleared completed jobs are preserved in an Archived tab rather than disappearing permanently.
- **Sort controls** — sort the job list by last updated, date added, file name, or file size.
- **Per-job priority** — promote individual jobs up the queue from the job detail panel.
- **Retry countdown** — failed jobs waiting to retry show "Retrying in 47m", updated live every 30 seconds.
- **Structured skip and failure explanations** — skip reasons and failure summaries are stored as structured payloads with a code, plain-English summary, measured values, and operator guidance; surfaced in the job detail panel before the raw FFmpeg log.
### UI & Design
- Font updated from Space Grotesk to DM Sans.
- Sidebar active state redesigned: a left accent bar replaces the filled background.
- Border radius tightened throughout with a more consistent scale across cards, buttons, and badges.
- Setup wizard refactored into composable step components.
#### Engine Control
- **Engine runtime modes** — Background (1 job), Balanced (half CPU count, capped at 4), and Throughput (half CPU count, uncapped). Manual concurrency and thread overrides available in the Advanced panel.
- **Drain mode** — stop accepting new jobs while letting active encodes finish cleanly.
- **Boot auto-analysis** — ffprobe runs on all queued jobs at startup so skip/transcode decisions are pre-computed before the engine starts.
### Infrastructure
- CI/CD workflows fully rewritten: Rust caching, TypeScript typechecking, and a frontend build shared across all platforms.
- Multi-arch Docker images are now published for `linux/amd64` and `linux/arm64`.
- Release binaries now ship as `.tar.gz` archives with SHA256 checksums; AppImage and `.app` bundles were removed.
- Dockerfile now uses the stable Rust image with pinned FFmpeg checksums.
- E2E test coverage added for all new features.
### UI Redesign
- Removed page `h1` headers; replaced the old header block with a thin engine control strip showing the status dot, Start/Pause/Stop, mode pills, About, and Logout in one row.
- Dashboard restructured around a compact stat row, savings summary card, and a larger Recent Activity panel.
- Log viewer groups entries by job into collapsible sections; system-level log lines render inline between groups.
- Setup wizard rebuilt inside the main app shell with a grayed sidebar, 2px solar progress line, and a welcome step (logo + tagline + Get Started) before the admin account form.
- Library selection redesigned around a flat recommendation list with Add buttons, selected-folder chips, and a Browse/manual path option; the old preview panel was removed.
- Statistics page uses recharts `AreaChart` for savings over time and `BarChart` for codec breakdown, replacing custom CSS bars.
- Hardware settings merged into the Transcoding tab. Notifications and Automation merged into one tab.
- Mobile layout: hamburger sidebar overlay, jobs table collapses date/priority columns below `md` breakpoint, stat cards use a 2×2 grid on small screens.
- Font updated from Space Grotesk to DM Sans; sidebar active state uses a left accent bar; border radius scale tightened throughout.
- Design system token compliance pass across all settings components: toggle switches, form labels, and text-on-color elements now use helios tokens exclusively.
- Analyzing job rows show an indeterminate shimmer instead of a static 0.0% label.
- Poll-based job state updates no longer overwrite terminal states that arrived via SSE.
## [v0.2.10-rc.1] - 2026-03-07
- Job lifecycle safety hardening: queued vs active cancel handling, active-job delete/restart blocking, batch-action conflict reporting, and stricter status/stat persistence.
- Output handling now supports mirrored `output_root` destinations plus temp-file promotion so replace mode preserves the last good artifact until encode, size, and quality gates pass.
- Scheduler, setup, and watch-folder parity updates shipped together: immediate schedule reevaluation, Intel Arc H.264 detection fix, H.264 setup option, canonicalized watch folders, and recursive watch configuration in the UI.
- Jobs and settings UX now expose per-job priority controls, output-root file settings, active-job-safe actions, and the Astro router deprecation cleanup.
- CI/CD rewrite for `0.2.10-rc.1`: cached Rust checks, frontend typecheck/build validation, multi-arch Docker publishing, and unified prerelease metadata handling across workflows.
- Release packaging cleanup: Linux and macOS now ship plain `.tar.gz` binaries, Windows ships `.exe`, and every release asset includes a SHA256 checksum file.
### Reliability & Stability
- Exponential retry backoff for failed jobs: 5 / 15 / 60 / 360 minute delays by attempt count.
- Orphaned temp file cleanup on startup: interrupted encodes and subtitle sidecar temp files no longer accumulate on disk.
- Fixed infinite analysis loop: jobs with an existing decision row are excluded from analysis batches, preventing transcodable jobs from being re-analyzed on every pass.
- Boot analysis processes jobs in batches of 100 from offset 0, fixing a pagination bug where transcodable jobs shifted out of later pages after earlier jobs were decided.
- Engine no longer auto-pauses when the queue empties; it stays Running and picks up new files as the watcher delivers them.
- Analysis semaphore serializes all analysis passes; watcher-triggered passes are dropped (not queued) when a pass is already running.
- Job stall detection added to surface encodes that stop making progress.
- Ctrl+C / SIGTERM exits cleanly after graceful shutdown. Background tasks no longer prevent process exit.
- Log table pruning: configurable retention period (default 30 days) prevents unbounded log growth.
- Auth session cleanup: expired sessions pruned on startup and every 24 hours.
- Resource endpoint caching: `/api/system/resources` cached 500ms to prevent redundant OS probes from multiple open tabs.
- `Drop` guard added to `in_flight_jobs` counter so it decrements correctly even on panic.
- Completed job detail no longer re-runs ffprobe on the source file; `encode_stats` is the authoritative source for post-encode metadata.
### Hardware & Encoding
- **Apple VideoToolbox** — encode commands now include `-allow_sw 1` (software fallback) and `format=yuv420p` (required pixel format), fixing all VideoToolbox encodes on macOS. HEVC output tagged as `hvc1` for Apple device compatibility.
- **Intel Arc** — VAAPI-first detection with `i915`/`xe` driver; QSV retained as last-resort fallback only.
- **Audio planning** — lossless codecs (TrueHD, MLP, DTS-HD, FLAC, PCM) trigger transcoding; standard Atmos/EAC3 at any bitrate now copies through without re-encoding.
- **libopus fallback** — audio transcoding for MKV now checks for `libopus` availability at runtime and falls back to AAC when it is absent (common on macOS FFmpeg builds).
- FFmpeg encode failures write the full error (last 20 lines of stderr) to the job log; failure explanations in the UI include VideoToolbox-specific patterns (`vt_compression`, `mediaserverd`, `no capable devices`).
### Backend Architecture
- Upgraded from Rust 2021 to **Rust 2024 edition**, MSRV set to 1.85.
- `sqlx` upgraded to 0.8 with `runtime-tokio-rustls`; `rand` upgraded to 0.9.
- Removed `async-trait`; all traits use native `async fn`. `trait-variant` added for object-safe `Arc<dyn ExecutionObserver>`.
- `server.rs` split into focused submodules: `auth`, `jobs`, `scan`, `settings`, `stats`, `system`, `sse`, `middleware`, `wizard`.
- `ffprobe` execution moved to `tokio::process::Command` with a 120-second timeout.
- Typed broadcast channels separate high-volume events (progress, logs) from low-volume system events (config, status).
- Poisoned cancellation lock recovery added to the orchestrator; oversized FFmpeg stderr lines truncated before logging.
- Invalid notification event JSON and invalid schedule day JSON now log a warning rather than silently disabling the target or treating it as empty.
- Database connection pool capped; OOM protection added to analysis batch queries via `LIMIT`/`OFFSET` pagination.
### Database
- `decisions` table extended with `reason_code` and `reason_payload_json` for structured skip reason storage.
- `job_failure_explanations` table added for structured failure explanations, with `legacy_summary` fallback for pre-0.3 rows.
- Index on `decisions(reason_code)` and `job_failure_explanations(code)` for fast filtering.
- All databases from v0.2.5 onwards upgrade automatically; no manual migration required.
### CI/CD & Tooling
- Nightly workflow: runs on every push to `main` after checks pass, builds all platforms, publishes `ghcr.io/brooklynloveszelda/alchemist:nightly` with `{VERSION}-nightly+{short-sha}` versioning.
- Shared reusable `build.yml` workflow so nightly and release builds use identical pipelines.
- `actionlint` added to `just release-check`.
- E2E reliability suite (`just test-e2e`) runs in CI after the frontend check passes.
- Windows contributor workflow documented and validated: `just install-w`, `just dev`, `just check`.
- `just release-check` covers fmt, clippy (`-D warnings -D clippy::unwrap_used -D clippy::expect_used`), tests, actionlint, web verify, docs build, E2E, and backend build in sequence.
- Release binaries ship as `.tar.gz` (Linux/macOS) and `.exe` (Windows), each with a SHA256 checksum. Multi-arch Docker images published for `linux/amd64` and `linux/arm64`.
## [v0.2.9] - 2026-03-06
- Runtime reliability pass: watcher/scanner hardening, resilient event consumers, config reload improvements, and live hardware refresh.

View File

@@ -2,14 +2,39 @@
Future improvements and features to consider for the project.
---
## Out of Scope — Explicitly Not Planned
These are deliberate design decisions, not omissions. Do not add them.
- **Custom FFmpeg flags / raw flag injection** — Alchemist is designed to be approachable and safe. Exposing raw FFmpeg arguments (whether per-profile, per-job, or in the conversion sandbox) would make it a footgun and undermine the beginner-first design. The encoding pipeline is the abstraction; users configure outcomes, not commands.
- **Distributed encoding across multiple machines** — Not a goal. Alchemist is a single-host tool. Multi-node orchestration is a different product.
---
## High Priority
### Behavior-Preserving Refactor Pass
- Keep the current product behavior exactly the same while improving internal structure
- Refactor `web/src/components/JobManager.tsx` into smaller components and hooks without changing screens, filters, polling, SSE updates, or job actions
- Centralize duplicated byte/time/reduction formatting logic into shared utilities and preserve current output formatting
- Preserve the current realtime model, but make ownership clearer: job/config/system events via SSE, resource metrics via polling
- Add regression coverage around planner decisions, watcher behavior, job lifecycle transitions, and decision explanation rendering before deeper refactors
- Document the current planner heuristics and hardware fallback rules so future cleanup does not accidentally change behavior
### Planning / Simulation Mode
- Add a first-class simulation flow that answers what Alchemist would transcode, remux, or skip without mutating the library
- Show estimated total bytes recoverable, action counts, top skip reasons, and per-file predicted actions
- Support comparing current settings against alternative profiles, codec targets, or threshold snapshots
- Reuse the scanner, analyzer, and planner, but stop before executor and promotion stages
### Per-File Encode History
- When a file has been processed more than once (retry, re-queue after settings change, manual re-run), show the full history of attempts in the job detail panel
- Each attempt should show: date, outcome (completed/failed/skipped), encode stats if applicable (size before/after, codec, duration), and failure reason if failed
- The data is already in the DB across `jobs`, `encode_stats`, and `job_failure_explanations` — this is primarily a UI feature
- Useful for understanding why a file kept failing, or comparing quality before/after a settings change
### E2E Test Coverage
- Expand Playwright tests for more UI flows
- Test job queue management scenarios
@@ -21,17 +46,143 @@ Future improvements and features to consider for the project.
- Verify encoder selection, fallback behavior, and quality/performance defaults
- Do not treat this as support-from-scratch: encoder wiring and hardware detection already exist
---
## Medium Priority
### Decision Clarity
- Replace loose skip/failure reason strings with structured UI/API payloads that include a code, plain-English summary, measured values, and operator guidance
- Show concise skip/failure summaries before raw logs in the job detail panel
- Make the jobs list communicate skip/failure class at a glance
### Power User Conversion / Remux Mode
**Target: 0.3.1**
#### Overview
- Introduce a conversion mode that allows users to upload a single file and perform customizable transcoding or remuxing operations using Alchemist's existing pipeline
- Exposes the same encoding parameters Alchemist uses internally — no raw flag injection
- Clear separation between remux mode (container-only, lossless) and transcode mode (re-encode)
#### Goals
- Provide a fast, interactive way to process single files
- Reuse Alchemist's existing job queue and worker system
- Avoid becoming a HandBrake clone; prioritize clarity over exhaustive configurability
#### Storage Structure
- Store temporary files under `~/.alchemist/temp/`
```text
~/.alchemist/
temp/
uploads/ # raw uploaded files
outputs/ # processed outputs
jobs/ # job metadata (JSON)
```
- Each job gets a unique ID (UUID or short hash)
- Files stored per job:
`uploads/{job_id}/input.ext`
`outputs/{job_id}/output.ext`
`jobs/{job_id}.json`
#### Core Workflow
1. User uploads file (drag-and-drop or file picker)
2. File is stored in `~/.alchemist/temp/uploads/{job_id}/`
3. Media is probed (`ffprobe`) and stream info is displayed
4. User configures conversion settings
5. User submits job
6. Job is added to Alchemist queue
7. Worker processes job using standard pipeline
8. Output is saved to `~/.alchemist/temp/outputs/{job_id}/`
9. User downloads result
#### UI Design Principles
- Must feel like a visual encoding editor
- No oversimplified presets as the primary UX
- All major encoding options exposed
- Clear separation between remux and transcode modes
#### UI Sections
##### 1. Input
- File upload (drag-and-drop)
- Display:
- container format
- video streams (codec, resolution, HDR info)
- audio streams (codec, channels)
- subtitle streams
##### 2. Output Container
- Options: `mkv`, `mp4`, `webm`, `mov`
##### 3. Video Settings
- Codec: `copy`, `h264`, `hevc`, `av1`
- Mode: CRF (quality-based) or Bitrate (kbps)
- Preset: `ultrafast` to `veryslow`
- Resolution: original, custom (width/height), scale factor
- HDR: preserve, tonemap to SDR, strip metadata
##### 4. Audio Settings
- Codec: `copy`, `aac`, `opus`, `mp3`
- Bitrate
- Channels (`auto`, stereo, 5.1, etc.)
##### 5. Subtitle Settings
- Options: `copy`, burn-in, remove
##### 6. Remux Mode
- Toggle: `[ ] Remux only (no re-encode)`
- Forces stream copy, disables all encoding options
- Use cases: container changes, stream compatibility fixes, zero quality loss operations
##### 7. Command Preview
- Display the generated FFmpeg command before execution
- Example: `ffmpeg -i input.mkv -c:v libaom-av1 -crf 28 -b:v 0 -c:a opus output.mkv`
- Read-only — for transparency and debugging, not for editing
#### Job System Integration
- Use the existing Alchemist job queue
- Treat each conversion as a standard job
- Stream logs live to the UI
#### Job Metadata Example
```json
{
"id": "abc123",
"input_path": "...",
"output_path": "...",
"mode": "transcode | remux",
"video": { "codec": "av1", "crf": 28, "preset": "slow" },
"audio": { "codec": "opus", "bitrate": 128 },
"container": "mkv",
"status": "queued"
}
```
#### Cleanup Strategy
- Auto-delete uploads after X hours
- Auto-delete outputs after download or timeout
- Enforce a max file size limit
- Run a periodic cleanup job that scans the temp directory
#### Security Considerations
- Sanitize filenames
- Prevent path traversal
- Validate file types via probing, not extension
- Isolate the temp directory
- Do not allow arbitrary file path input
#### Non-Goals
- Not a beginner-focused tool
- Not a replacement for full automation workflows
- Not a cloud encoding service; no public hosting assumed
- No raw FFmpeg flag injection (see Out of Scope)
### Library Intelligence
- Expand recommendations beyond duplicate detection into remux-only opportunities, wasteful audio layouts, commentary/descriptive-track cleanup, and duplicate-ish title variants
- Keep the feature focused on storage and library quality, not general media management
### Auto-Priority Rules
- Define rules that automatically assign queue priority based on file attributes
- Rule conditions: file path pattern (glob), file age, file size, source watch folder
- Example: "anything under `/movies/` gets priority 2", "files over 20 GB get priority 1"
- Rules evaluated at enqueue time; manual priority overrides still win
- Configured in Settings alongside other library behavior
### Performance Optimizations
- Profile scanner/analyzer hot paths before changing behavior
- Only tune connection pooling after measuring database contention under load
@@ -47,18 +198,7 @@ Future improvements and features to consider for the project.
the job detail panel alongside existing encode stats
- Do not normalize if audio is being copied (copy mode bypasses this)
### Retry Backoff Visibility
- The retry backoff schedule already exists in the backend
(5 min / 15 min / 60 min / 360 min by attempt count) but is
completely invisible in the UI
- Failed jobs waiting to retry should show a clear countdown or
timestamp: "Retrying in 12 minutes" or "Next retry at 3:45 PM"
- The Jobs page Failed tab should distinguish between
"failed — will retry" and "failed — gave up" at a glance
- The job detail panel should show attempt count and next retry time
### UI Improvements
- Improve mobile responsiveness
- Add keyboard shortcuts for common actions
### Notification Improvements
@@ -81,10 +221,27 @@ Future improvements and features to consider for the project.
- **Add email support** — SMTP with TLS. Lower priority than Telegram.
Most self-hosters already have Discord or Telegram.
---
## Low Priority
### API Token Authentication + API Documentation
- Add support for static bearer tokens as an alternative to session cookies
- Enables programmatic access from scripts, home automation (Home Assistant, n8n), and CLI tools without managing session state
- Tokens generated and revoked from Settings; no expiry by default, revocable any time
- Expand API documentation to cover all endpoints with request/response examples
### Passthrough Mode
- A toggle that keeps all watch folders and watcher active but prevents the planner from queuing new jobs
- Different from Pause — Pause stops active encodes; Passthrough lets the system observe and index the library without touching anything
- Useful when testing settings or onboarding a new library without triggering encodes immediately
### Base URL / Subpath Configuration
- Allow Alchemist to be served at a non-root path (e.g. `/alchemist/`) via `ALCHEMIST_BASE_URL`
- Common self-hosting pattern for reverse proxy setups running multiple services on one domain
- Low urgency — most users run Alchemist on a dedicated subdomain or port
### Features from DESIGN_PHILOSOPHY.md
- Consider WebSocket alternative to SSE for bidirectional communication
- Add batch job templates
### Code Quality
@@ -96,7 +253,6 @@ Future improvements and features to consider for the project.
- Add architecture diagrams
- Add contributor guide with development setup
- Video tutorials for common workflows
- API client examples in multiple languages
### Distribution
- Add Homebrew formula
@@ -104,10 +260,11 @@ Future improvements and features to consider for the project.
- Add Flatpak/Snap packages
- Improve Windows installer (WiX) with auto-updates
---
## Completed (Recent)
- [x] Split server.rs into modules
- [x] Add API versioning (/api/v1/)
- [x] Add typed broadcast channels
- [x] Add security headers middleware
- [x] Add database query timeouts
@@ -115,10 +272,20 @@ Future improvements and features to consider for the project.
- [x] Handle SSE lagged events in frontend
- [x] Create FFmpeg integration tests
- [x] Expand documentation site
- [x] Create OpenAPI spec
- [x] Pin MSRV in Cargo.toml
- [x] Add schema versioning for migrations
- [x] Enable SQLite WAL mode
- [x] Add theme persistence and selection
- [x] Add job history filtering and search
- [x] Add subtitle extraction sidecars
- [x] Decision clarity — structured skip/failure explanations with codes, plain-English summaries, measured values, and operator guidance
- [x] Retry backoff visibility — countdown on failed jobs, attempt count in job detail
- [x] Per-library profiles (Space Saver, Quality First, Balanced, Streaming)
- [x] Engine runtime modes (Background / Balanced / Throughput) with drain support
- [x] Container remuxing (MP4 → MKV lossless)
- [x] Stream rules (commentary stripping, language filtering, default-only audio)
- [x] VMAF quality gating
- [x] Library Intelligence duplicate detection
- [x] Library Doctor health scanning
- [x] Boot auto-analysis
- [x] Mobile layout

View File

@@ -3,118 +3,100 @@ title: Changelog
description: Release history for Alchemist.
---
## [0.3.0-rc.3] - 2026-04-04
## [0.3.0] - 2026-04-05
### Release Engineering
- Added Windows-specific contributor scripts for the core `just install-w`, `just dev`, and `just check` path, while leaving broader utility and release recipes Unix-first for now.
- Updated the release checklist and contributor docs so RC.2 clearly documents the supported Windows workflow and the manual Windows verification follow-up required before stable.
### Regression Coverage
- Added a startup regression test for the RC.1 security fix so a config parse failure on a configured instance with existing users does not reopen setup-only access.
- Expanded Playwright stabilization coverage for retry countdown rendering, Library & Intake manual scan success/failure surfacing, and completed job detail rendering from persisted encode stats.
## [0.3.0-rc.1] - 2026-04-04
### Rust & Backend
- Upgraded from Rust 2021 to 2024, set the MSRV to 1.85, upgraded `sqlx` to `0.8` with `runtime-tokio-rustls`, and upgraded `rand` to `0.9`.
- Removed `async-trait`, adopted native `async fn` in traits, and added `trait-variant` so `ExecutionObserver` remained object-safe behind `Arc<dyn ...>`.
- Fixed the Apple VideoToolbox probe by using synthetic `lavfi` frames with `format=yuv420p` and `-allow_sw 1`, allowing HEVC and AV1 detection to succeed on Apple Silicon.
- Split `server.rs` into `src/server/` submodules for auth, jobs, scan, settings, stats, system, SSE, middleware, wizard, and tests.
- Moved `ffprobe` execution to `tokio::process::Command` with a 120-second timeout.
- Added poisoned cancellation lock recovery in the orchestrator and truncated oversized FFmpeg stderr lines before logging.
- Invalid notification event JSON now warned instead of silently disabling the target, and invalid schedule day JSON now warned instead of being treated as empty.
### UI & Frontend
- Reworked the app shell by removing page `h1` headers, replacing the old header block with a thin engine control strip, and dropping the sidebar icon container for wordmark-only branding.
- Flattened engine controls into one horizontal row with the status dot, Start/Pause/Stop actions, a divider, About, and Logout.
- Moved the Background, Balanced, and Throughput mode pills into Settings -> Runtime and wired them to the engine mode API.
- Rebuilt the setup wizard inside the main app shell with a grayed sidebar, removed the centered card layout, and replaced progress with a 2px solar line at the top of the content area.
- Setup wizard errors now surfaced as toast notifications instead of an inline error block below the content.
- Redesigned library selection around a flat recommendation list with Add buttons, selected-folder chips, and equal Browse/manual path entry; the preview panel was removed.
- Restructured the dashboard around a compact stat row, a savings summary, and a larger Recent Activity panel.
- Updated the log viewer to group entries by `job_id` into collapsible job sections while rendering system logs inline.
- Normalized job status badges to sentence case without letter spacing, aligned table column headers, and standardized the job detail modal radius and section headers.
- Cleaned banned CSS patterns from `ServerDirectoryPicker`, `ToastRegion`, and `SavingsOverview`, and modernized the login page with wordmark-only branding and tighter field styling.
- Darkened the global background gradient slightly, reduced scrollbar width to 6px, increased border radius one step across cards and buttons, and gave inputs more contrast against the page.
### CI/CD
- Added a nightly workflow that runs on every push to `main` after Rust checks pass, builds all platforms, publishes `ghcr.io/bybrooklyn/alchemist:nightly`, and replaces the previous nightly pre-release with `{VERSION}-nightly+{short-sha}`.
- Extracted a shared reusable `build.yml` workflow so nightly and release builds use the same pipeline.
- Simplified `docker.yml` to PR preview builds only, removed the `workflow_run` trigger, and removed the `main` branch trigger from `ci.yml`.
- Frontend validation in CI now runs the e2e reliability suite after the frontend check passes.
### Docs & Tooling
- Added `CLAUDE.md` and `GEMINI.md` as agent context files, added `CONTRIBUTING.md` with GPL-3.0 CLA terms, and started `CHANGELOG.md`.
- Hardened the `justfile` with safer process cleanup, a stronger `db-reset`, and `--frozen-lockfile` docs installation.
## [v0.2.10-rc.5] - 2026-03-22
### Runtime & Queue Control
- Engine runtime modes now support `background`, `balanced`, and `throughput`, with manual concurrency overrides and drain/resume controls exposed through the API and dashboard header.
- Engine status reporting now includes pause source, drain state, concurrent-limit metadata, and mode visibility for troubleshooting.
- Queue management continued to harden with safer active-job controls, clearer failure surfacing, and better per-job operational feedback.
### Media Processing & Library Health
- VAAPI-first Intel handling, remux planning, subtitle sidecars, and library health issue reporting were expanded across the planner, FFmpeg integration, and dashboard.
- Hardware detection and probe logging were improved to make CPU/GPU backend selection and diagnostics easier to understand.
- Stream rules landed for commentary stripping, audio-language filtering, and keeping only the default audio track when needed.
### Paths, Setup & Docs
- Default config and database paths were normalized around the `alchemist` runtime home, and the repo now ships a `justfile` for common dev, release, Docker, and database workflows.
- The docs site moved onto Starlight and now builds locally with a proper content config, localized collection wiring, and a corrected splash-page schema.
- Release automation now bumps repo-wide version manifests, supports checkpoint commits before release validation, and isolates web-e2e onto a separate port so a local server on `3000` does not block release verification.
### CI/CD & Release Tooling
- Docker publishing is now gated behind successful validation, and local release verification covers Rust checks/tests, frontend verification, docs build, `actionlint`, and the reliability Playwright suite.
- `just update` does not create the release commit, git tag, or push until the full validation gate passes.
## [v0.2.10-rc.2] - 2026-03-21
### Stability & Reliability
- VMAF quality gating: encodes falling below a configurable minimum score are now rejected rather than silently promoted.
- Exponential retry backoff for failed jobs: 5 / 15 / 60 / 360 minute delays based on attempt count prevent tight failure loops.
- Orphaned temp file cleanup on startup: interrupted encodes no longer leave `.alchemist.tmp` files on disk indefinitely.
- Log table pruning: configurable retention period (default 30 days) prevents unbounded log growth on busy servers.
- Auth session cleanup: expired sessions are pruned on startup and every 24 hours.
- Resource endpoint caching: `/api/system/resources` is cached for 500ms to prevent redundant OS probes from multiple open browser tabs.
### Security
- Fixed a critical bug where a config parse failure on a configured instance would re-enable unauthenticated setup endpoints (filesystem browse, settings bundle) for any network client.
- Session cookies are no longer marked `Secure` by default, which was breaking login over plain HTTP/LAN. Opt in with `ALCHEMIST_COOKIE_SECURE=true` for reverse-proxy deployments.
- `/api/fs/*` filesystem browsing is now restricted to loopback connections only during the initial setup flow.
- Proxy header handling hardened with explicit trust configuration for reverse-proxy deployments.
### New Features
- Per-library profiles: each watch folder can have its own transcoding profile, with four built-in presets (Space Saver, Quality First, Balanced, Streaming) usable as starting points.
- Storage savings dashboard: the Stats page now shows total space recovered, average reduction percentage, a savings-over-time chart, and per-codec breakdowns.
- Library Doctor: scan your library for corrupt or broken files directly from System Settings.
- `/api/jobs` added as a canonical alias for `/api/jobs/table`.
### Beginner Experience
- Plain-English skip reasons: skipped jobs now show a human-readable explanation with technical detail available in an expandable section.
- Auto-redirect to the setup wizard for first-time users with no watch directories configured.
- Setup wizard field descriptions: CRF, BPP, concurrent jobs, and CPU preset now include plain-English explanations inline.
- Telemetry is now opt-in by default, with a detailed explanation of exactly what is collected.
#### Library & Encoding
- **Per-library profiles** — each watch folder gets its own transcoding profile. Four built-in presets (Space Saver, Quality First, Balanced, Streaming) are ready to use or customize.
- **Container remuxing** — files already in the target codec but wrapped in MP4/MOV are remuxed to MKV losslessly, skipping a full re-encode.
- **Subtitle sidecar extraction** — text-based subtitle tracks (SRT, ASS, VTT) can be extracted as separate files alongside the output rather than muxed in.
- **Stream rules** — strip audio tracks by title keyword (e.g. commentary tracks), filter by language code, or keep only the default audio track.
- **VMAF quality gating** — encodes scoring below a configurable threshold are rejected and the source is preserved.
- **Library Intelligence** — duplicate detection surfaces files with matching stems across the library.
- **Library Doctor** — health scanning detects corrupt or broken files directly from System Settings.
- **Mirrored output root** — write transcoded files to a separate directory tree that mirrors the source structure, rather than alongside the source.
### Job Management
- Skipped tab: dedicated tab in the Job Manager for skipped jobs.
- Archived tab: cleared completed jobs are now visible in an Archived tab rather than disappearing permanently.
- Sort controls: the job list can now be sorted by last updated, date added, file name, or file size.
#### Job Management
- **Skipped tab** — dedicated tab for skipped jobs with structured skip reasons.
- **Archived tab** — cleared completed jobs are preserved in an Archived tab rather than disappearing permanently.
- **Sort controls** — sort the job list by last updated, date added, file name, or file size.
- **Per-job priority** — promote individual jobs up the queue from the job detail panel.
- **Retry countdown** — failed jobs waiting to retry show "Retrying in 47m", updated live every 30 seconds.
- **Structured skip and failure explanations** — skip reasons and failure summaries are stored as structured payloads with a code, plain-English summary, measured values, and operator guidance; surfaced in the job detail panel before the raw FFmpeg log.
### UI & Design
- Font updated from Space Grotesk to DM Sans.
- Sidebar active state redesigned: a left accent bar replaces the filled background.
- Border radius tightened throughout with a more consistent scale across cards, buttons, and badges.
- Setup wizard refactored into composable step components.
#### Engine Control
- **Engine runtime modes** — Background (1 job), Balanced (half CPU count, capped at 4), and Throughput (half CPU count, uncapped). Manual concurrency and thread overrides available in the Advanced panel.
- **Drain mode** — stop accepting new jobs while letting active encodes finish cleanly.
- **Boot auto-analysis** — ffprobe runs on all queued jobs at startup so skip/transcode decisions are pre-computed before the engine starts.
### Infrastructure
- CI/CD workflows fully rewritten: Rust caching, TypeScript typechecking, and a frontend build shared across all platforms.
- Multi-arch Docker images are now published for `linux/amd64` and `linux/arm64`.
- Release binaries now ship as `.tar.gz` archives with SHA256 checksums; AppImage and `.app` bundles were removed.
- Dockerfile now uses the stable Rust image with pinned FFmpeg checksums.
- E2E test coverage added for all new features.
### UI Redesign
- Removed page `h1` headers; replaced the old header block with a thin engine control strip showing the status dot, Start/Pause/Stop, mode pills, About, and Logout in one row.
- Dashboard restructured around a compact stat row, savings summary card, and a larger Recent Activity panel.
- Log viewer groups entries by job into collapsible sections; system-level log lines render inline between groups.
- Setup wizard rebuilt inside the main app shell with a grayed sidebar, 2px solar progress line, and a welcome step (logo + tagline + Get Started) before the admin account form.
- Library selection redesigned around a flat recommendation list with Add buttons, selected-folder chips, and a Browse/manual path option; the old preview panel was removed.
- Statistics page uses recharts `AreaChart` for savings over time and `BarChart` for codec breakdown, replacing custom CSS bars.
- Hardware settings merged into the Transcoding tab. Notifications and Automation merged into one tab.
- Mobile layout: hamburger sidebar overlay, jobs table collapses date/priority columns below `md` breakpoint, stat cards use a 2×2 grid on small screens.
- Font updated from Space Grotesk to DM Sans; sidebar active state uses a left accent bar; border radius scale tightened throughout.
- Design system token compliance pass across all settings components: toggle switches, form labels, and text-on-color elements now use helios tokens exclusively.
- Analyzing job rows show an indeterminate shimmer instead of a static 0.0% label.
- Poll-based job state updates no longer overwrite terminal states that arrived via SSE.
## [v0.2.10-rc.1] - 2026-03-07
- Job lifecycle safety hardening: queued vs active cancel handling, active-job delete/restart blocking, batch-action conflict reporting, and stricter status/stat persistence.
- Output handling now supports mirrored `output_root` destinations plus temp-file promotion so replace mode preserves the last good artifact until encode, size, and quality gates pass.
- Scheduler, setup, and watch-folder parity updates shipped together: immediate schedule reevaluation, Intel Arc H.264 detection fix, H.264 setup option, canonicalized watch folders, and recursive watch configuration in the UI.
- Jobs and settings UX now expose per-job priority controls, output-root file settings, active-job-safe actions, and the Astro router deprecation cleanup.
- CI/CD rewrite for `0.2.10-rc.1`: cached Rust checks, frontend typecheck/build validation, multi-arch Docker publishing, and unified prerelease metadata handling across workflows.
- Release packaging cleanup: Linux and macOS now ship plain `.tar.gz` binaries, Windows ships `.exe`, and every release asset includes a SHA256 checksum file.
### Reliability & Stability
- Exponential retry backoff for failed jobs: 5 / 15 / 60 / 360 minute delays by attempt count.
- Orphaned temp file cleanup on startup: interrupted encodes and subtitle sidecar temp files no longer accumulate on disk.
- Fixed infinite analysis loop: jobs with an existing decision row are excluded from analysis batches, preventing transcodable jobs from being re-analyzed on every pass.
- Boot analysis processes jobs in batches of 100 from offset 0, fixing a pagination bug where transcodable jobs shifted out of later pages after earlier jobs were decided.
- Engine no longer auto-pauses when the queue empties; it stays Running and picks up new files as the watcher delivers them.
- Analysis semaphore serializes all analysis passes; watcher-triggered passes are dropped (not queued) when a pass is already running.
- Job stall detection added to surface encodes that stop making progress.
- Ctrl+C / SIGTERM exits cleanly after graceful shutdown. Background tasks no longer prevent process exit.
- Log table pruning: configurable retention period (default 30 days) prevents unbounded log growth.
- Auth session cleanup: expired sessions pruned on startup and every 24 hours.
- Resource endpoint caching: `/api/system/resources` cached 500ms to prevent redundant OS probes from multiple open tabs.
- `Drop` guard added to `in_flight_jobs` counter so it decrements correctly even on panic.
- Completed job detail no longer re-runs ffprobe on the source file; `encode_stats` is the authoritative source for post-encode metadata.
### Hardware & Encoding
- **Apple VideoToolbox** — encode commands now include `-allow_sw 1` (software fallback) and `format=yuv420p` (required pixel format), fixing all VideoToolbox encodes on macOS. HEVC output tagged as `hvc1` for Apple device compatibility.
- **Intel Arc** — VAAPI-first detection with `i915`/`xe` driver; QSV retained as last-resort fallback only.
- **Audio planning** — lossless codecs (TrueHD, MLP, DTS-HD, FLAC, PCM) trigger transcoding; standard Atmos/EAC3 at any bitrate now copies through without re-encoding.
- **libopus fallback** — audio transcoding for MKV now checks for `libopus` availability at runtime and falls back to AAC when it is absent (common on macOS FFmpeg builds).
- FFmpeg encode failures write the full error (last 20 lines of stderr) to the job log; failure explanations in the UI include VideoToolbox-specific patterns (`vt_compression`, `mediaserverd`, `no capable devices`).
### Backend Architecture
- Upgraded from Rust 2021 to **Rust 2024 edition**, MSRV set to 1.85.
- `sqlx` upgraded to 0.8 with `runtime-tokio-rustls`; `rand` upgraded to 0.9.
- Removed `async-trait`; all traits use native `async fn`. `trait-variant` added for object-safe `Arc<dyn ExecutionObserver>`.
- `server.rs` split into focused submodules: `auth`, `jobs`, `scan`, `settings`, `stats`, `system`, `sse`, `middleware`, `wizard`.
- `ffprobe` execution moved to `tokio::process::Command` with a 120-second timeout.
- Typed broadcast channels separate high-volume events (progress, logs) from low-volume system events (config, status).
- Poisoned cancellation lock recovery added to the orchestrator; oversized FFmpeg stderr lines truncated before logging.
- Invalid notification event JSON and invalid schedule day JSON now log a warning rather than silently disabling the target or treating it as empty.
- Database connection pool capped; OOM protection added to analysis batch queries via `LIMIT`/`OFFSET` pagination.
### Database
- `decisions` table extended with `reason_code` and `reason_payload_json` for structured skip reason storage.
- `job_failure_explanations` table added for structured failure explanations, with `legacy_summary` fallback for pre-0.3 rows.
- Index on `decisions(reason_code)` and `job_failure_explanations(code)` for fast filtering.
- All databases from v0.2.5 onwards upgrade automatically; no manual migration required.
### CI/CD & Tooling
- Nightly workflow: runs on every push to `main` after checks pass, builds all platforms, publishes `ghcr.io/brooklynloveszelda/alchemist:nightly` with `{VERSION}-nightly+{short-sha}` versioning.
- Shared reusable `build.yml` workflow so nightly and release builds use identical pipelines.
- `actionlint` added to `just release-check`.
- E2E reliability suite (`just test-e2e`) runs in CI after the frontend check passes.
- Windows contributor workflow documented and validated: `just install-w`, `just dev`, `just check`.
- `just release-check` covers fmt, clippy (`-D warnings -D clippy::unwrap_used -D clippy::expect_used`), tests, actionlint, web verify, docs build, E2E, and backend build in sequence.
- Release binaries ship as `.tar.gz` (Linux/macOS) and `.exe` (Windows), each with a SHA256 checksum. Multi-arch Docker images published for `linux/amd64` and `linux/arm64`.
## [v0.2.9] - 2026-03-06
- Runtime reliability pass: watcher/scanner hardening, resilient event consumers, config reload improvements, and live hardware refresh.

View File

@@ -8,7 +8,6 @@ const baseUrl = process.env.DOCS_BASE_URL ?? '/';
const config: Config = {
title: 'Alchemist',
tagline: 'Self-hosted video transcoding automation. Point it at your library. Walk away.',
favicon: 'img/favicon.ico',
future: {
v4: true,

View File

@@ -1,4 +1,3 @@
# Images
- `favicon.ico` — site favicon (replace with Alchemist favicon)
- `social-card.png` — OG social card (add before launch)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -360,7 +360,7 @@ export default function AppearanceSettings() {
<div key={category.id} className="flex flex-col gap-4">
<div className="flex items-center gap-2 px-1">
{category.icon}
<h4 className="text-xs font-bold uppercase tracking-widest text-helios-slate/60">
<h4 className="text-xs font-medium text-helios-slate/60">
{category.label}
</h4>
</div>
@@ -428,7 +428,7 @@ export default function AppearanceSettings() {
{isActive && (
<div className="absolute top-3 right-3 flex items-center gap-1.5 bg-helios-solar text-helios-mist px-2.5 py-1 rounded-full shadow">
<CheckCircle2 size={12} />
<span className="text-[9px] font-bold uppercase tracking-widest">Active</span>
<span className="text-xs font-medium">Active</span>
</div>
)}
</button>

View File

@@ -34,7 +34,7 @@ export class ErrorBoundary extends Component<Props, State> {
return (
<div className="flex flex-col items-center justify-center p-8 bg-helios-background border border-helios-red/50 rounded-lg shadow-sm text-center w-full min-h-[300px]">
<AlertCircle className="w-12 h-12 text-helios-red mb-4" />
<h2 className="text-xl font-bold text-white mb-2">Something went wrong</h2>
<h2 className="text-xl font-bold text-helios-ink mb-2">Something went wrong</h2>
<p className="text-helios-text/70 mb-4 max-w-md">
The {this.props.moduleName || "component"} encountered an unexpected error and could not be displayed.
</p>
@@ -43,7 +43,7 @@ export class ErrorBoundary extends Component<Props, State> {
</div>
<button
onClick={() => window.location.reload()}
className="px-6 py-2 bg-helios-orange hover:bg-helios-orange/80 text-white font-medium rounded transition"
className="px-6 py-2 bg-helios-orange hover:bg-helios-orange/80 text-helios-main font-medium rounded transition"
>
Reload Page
</button>

View File

@@ -73,7 +73,7 @@ export default function FileSettings() {
<div className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold uppercase text-helios-slate mb-1">Output Suffix</label>
<label className="block text-xs font-medium text-helios-slate mb-1">Output Suffix</label>
<input
type="text"
value={settings.output_suffix}
@@ -87,7 +87,7 @@ export default function FileSettings() {
</p>
</div>
<div>
<label className="block text-xs font-bold uppercase text-helios-slate mb-1">Extension</label>
<label className="block text-xs font-medium text-helios-slate mb-1">Extension</label>
<select
value={settings.output_extension}
onChange={e => setSettings({ ...settings, output_extension: e.target.value })}
@@ -100,7 +100,7 @@ export default function FileSettings() {
</div>
<div>
<label className="block text-xs font-bold uppercase text-helios-slate mb-1">Output Root</label>
<label className="block text-xs font-medium text-helios-slate mb-1">Output Root</label>
<input
type="text"
value={settings.output_root ?? ""}
@@ -114,7 +114,7 @@ export default function FileSettings() {
</div>
<div>
<label className="block text-xs font-bold uppercase text-helios-slate mb-1">Existing Output Policy</label>
<label className="block text-xs font-medium text-helios-slate mb-1">Existing Output Policy</label>
<select
value={settings.replace_strategy}
onChange={e => setSettings({ ...settings, replace_strategy: e.target.value })}

View File

@@ -438,7 +438,7 @@ export default function HardwareSettings() {
disabled={saving}
className="sr-only peer"
/>
<div className="w-11 h-6 rounded-full bg-helios-line/20 peer-focus:outline-none after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:bg-white after:content-[''] after:transition-all peer-checked:after:translate-x-full peer-checked:bg-helios-solar peer-disabled:cursor-not-allowed peer-disabled:opacity-60"></div>
<div className="w-11 h-6 rounded-full bg-helios-line/20 peer-focus:outline-none after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:bg-helios-ink after:content-[''] after:transition-all peer-checked:after:translate-x-full peer-checked:bg-helios-solar peer-disabled:cursor-not-allowed peer-disabled:opacity-60"></div>
</label>
</div>
@@ -492,7 +492,7 @@ export default function HardwareSettings() {
disabled={saving}
className="sr-only peer"
/>
<div className="w-11 h-6 rounded-full bg-helios-line/20 peer-focus:outline-none after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:bg-white after:content-[''] after:transition-all peer-checked:after:translate-x-full peer-checked:bg-helios-solar peer-disabled:cursor-not-allowed peer-disabled:opacity-60"></div>
<div className="w-11 h-6 rounded-full bg-helios-line/20 peer-focus:outline-none after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:bg-helios-ink after:content-[''] after:transition-all peer-checked:after:translate-x-full peer-checked:bg-helios-solar peer-disabled:cursor-not-allowed peer-disabled:opacity-60"></div>
</label>
</div>

View File

@@ -356,7 +356,7 @@ export default function LogViewer() {
? "text-status-error"
: log.level.toLowerCase().includes("warn")
? "text-amber-400"
: "text-white/90"
: "text-helios-ink"
)}>
{log.message}
</span>
@@ -411,7 +411,7 @@ export default function LogViewer() {
? "text-status-error"
: log.level.toLowerCase().includes("warn")
? "text-amber-400"
: "text-white/90"
: "text-helios-ink"
)}>
{log.message}
</span>

View File

@@ -138,7 +138,7 @@ export default function NotificationSettings() {
<div className="flex justify-end mb-6">
<button
onClick={() => setShowForm(!showForm)}
className="flex items-center gap-2 px-3 py-1.5 bg-helios-surface border border-helios-line/30 hover:bg-helios-surface-soft text-helios-ink rounded-lg text-xs font-bold uppercase tracking-wider transition-colors"
className="flex items-center gap-2 px-3 py-1.5 bg-helios-surface border border-helios-line/30 hover:bg-helios-surface-soft text-helios-ink rounded-lg text-xs font-medium transition-colors"
>
<Plus size={14} />
{showForm ? "Cancel" : "Add Target"}
@@ -155,7 +155,7 @@ export default function NotificationSettings() {
<form onSubmit={handleAdd} className="bg-helios-surface-soft p-4 rounded-xl space-y-4 border border-helios-line/20 mb-6">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold uppercase text-helios-slate mb-1">Name</label>
<label className="block text-xs font-medium text-helios-slate mb-1">Name</label>
<input
value={newName}
onChange={e => setNewName(e.target.value)}
@@ -165,7 +165,7 @@ export default function NotificationSettings() {
/>
</div>
<div>
<label className="block text-xs font-bold uppercase text-helios-slate mb-1">Type</label>
<label className="block text-xs font-medium text-helios-slate mb-1">Type</label>
<select
value={newType}
onChange={e => setNewType(e.target.value as NotificationTarget["target_type"])}
@@ -181,7 +181,7 @@ export default function NotificationSettings() {
</div>
<div>
<label className="block text-xs font-bold uppercase text-helios-slate mb-1">Endpoint URL</label>
<label className="block text-xs font-medium text-helios-slate mb-1">Endpoint URL</label>
<input
value={newUrl}
onChange={e => setNewUrl(e.target.value)}
@@ -192,7 +192,7 @@ export default function NotificationSettings() {
</div>
<div>
<label className="block text-xs font-bold uppercase text-helios-slate mb-1">Auth Token (Optional)</label>
<label className="block text-xs font-medium text-helios-slate mb-1">Auth Token (Optional)</label>
<input
value={newToken}
onChange={e => setNewToken(e.target.value)}
@@ -202,7 +202,7 @@ export default function NotificationSettings() {
</div>
<div>
<label className="block text-xs font-bold uppercase text-helios-slate mb-2">Events</label>
<label className="block text-xs font-medium text-helios-slate mb-2">Events</label>
<div className="flex gap-4 flex-wrap">
{["completed", "failed", "queued"].map(evt => (
<label key={evt} className="flex items-center gap-2 text-sm text-helios-ink cursor-pointer">

View File

@@ -75,7 +75,7 @@ export default function QualitySettings() {
<div className="rounded-lg border border-helios-line/20 bg-helios-surface-soft/60 p-4 flex items-center justify-between">
<div>
<p className="text-xs font-bold uppercase tracking-wider text-helios-slate">Enable VMAF</p>
<p className="text-xs font-medium text-helios-slate">Enable VMAF</p>
<p className="text-xs text-helios-slate mt-1">Compute a quality score after encoding.</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
@@ -94,12 +94,12 @@ export default function QualitySettings() {
})}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-helios-line/20 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-helios-solar"></div>
<div className="w-11 h-6 bg-helios-line/20 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-helios-ink after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-helios-ink after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-helios-solar"></div>
</label>
</div>
<div className="space-y-3">
<label className="text-xs font-bold uppercase tracking-wider text-helios-slate">Minimum VMAF Score</label>
<label className="text-xs font-medium text-helios-slate">Minimum VMAF Score</label>
<input
type="number"
min="0"
@@ -122,7 +122,7 @@ export default function QualitySettings() {
<div className="rounded-lg border border-helios-line/20 bg-helios-surface-soft/60 p-4 flex items-center justify-between">
<div>
<p className="text-xs font-bold uppercase tracking-wider text-helios-slate">Revert on Low Quality</p>
<p className="text-xs font-medium text-helios-slate">Revert on Low Quality</p>
<p className="text-xs text-helios-slate mt-1">Keep the source if the VMAF score drops below the threshold.</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
@@ -141,7 +141,7 @@ export default function QualitySettings() {
})}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-helios-line/20 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-helios-solar"></div>
<div className="w-11 h-6 bg-helios-line/20 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-helios-ink after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-helios-ink after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-helios-solar"></div>
</label>
</div>

View File

@@ -100,7 +100,7 @@ export default function ScheduleSettings() {
<div className="flex justify-end mb-6">
<button
onClick={() => setShowForm(!showForm)}
className="flex items-center gap-2 px-3 py-1.5 bg-helios-surface border border-helios-line/30 hover:bg-helios-surface-soft text-helios-ink rounded-lg text-xs font-bold uppercase tracking-wider transition-colors"
className="flex items-center gap-2 px-3 py-1.5 bg-helios-surface border border-helios-line/30 hover:bg-helios-surface-soft text-helios-ink rounded-lg text-xs font-medium transition-colors"
>
<Plus size={14} />
{showForm ? "Cancel" : "Add Schedule"}
@@ -135,7 +135,7 @@ export default function ScheduleSettings() {
<form onSubmit={handleAdd} className="bg-helios-surface-soft p-4 rounded-xl space-y-4 border border-helios-line/20 mb-6">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold uppercase text-helios-slate mb-1">Start Time</label>
<label className="block text-xs font-medium text-helios-slate mb-1">Start Time</label>
<input
type="time"
value={newStart}
@@ -145,7 +145,7 @@ export default function ScheduleSettings() {
/>
</div>
<div>
<label className="block text-xs font-bold uppercase text-helios-slate mb-1">End Time</label>
<label className="block text-xs font-medium text-helios-slate mb-1">End Time</label>
<input
type="time"
value={newEnd}
@@ -157,7 +157,7 @@ export default function ScheduleSettings() {
</div>
<div>
<label className="block text-xs font-bold uppercase text-helios-slate mb-2">Days</label>
<label className="block text-xs font-medium text-helios-slate mb-2">Days</label>
<div className="flex gap-2 flex-wrap">
{DAYS.map((day, idx) => (
<button

View File

@@ -269,7 +269,7 @@ export default function StatsCharts() {
className="absolute inset-y-0 left-0 bg-gradient-to-r from-emerald-500 to-emerald-400 rounded-full transition-all duration-1000"
style={{ width: `${100 - parseFloat(savingsPercent)}%` }}
/>
<div className="absolute inset-0 flex items-center justify-center text-sm font-bold text-white drop-shadow">
<div className="absolute inset-0 flex items-center justify-center text-sm font-bold text-helios-main drop-shadow">
{formatBytes(stats.total_output_bytes)} / {formatBytes(stats.total_input_bytes)}
</div>
</div>

View File

@@ -273,7 +273,7 @@ export default function SystemSettings() {
onChange={(e) => setSettings({ ...settings, watch_enabled: e.target.checked })}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-helios-line/20 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-helios-solar"></div>
<div className="w-11 h-6 bg-helios-line/20 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-helios-ink after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-helios-ink after:border-helios-line/30 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-helios-solar"></div>
</label>
</div>
</div>
@@ -296,7 +296,7 @@ export default function SystemSettings() {
onChange={(e) => setSettings({ ...settings, enable_telemetry: e.target.checked })}
className="sr-only peer"
/>
<div className="w-11 h-6 rounded-full bg-helios-line/20 peer-focus:outline-none after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:content-[''] after:transition-all peer-checked:after:translate-x-full peer-checked:after:border-white peer-checked:bg-helios-solar rtl:peer-checked:after:-translate-x-full peer-disabled:cursor-not-allowed peer-disabled:opacity-60"></div>
<div className="w-11 h-6 rounded-full bg-helios-line/20 peer-focus:outline-none after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-helios-line/30 after:bg-helios-ink after:content-[''] after:transition-all peer-checked:after:translate-x-full peer-checked:after:border-helios-ink peer-checked:bg-helios-solar rtl:peer-checked:after:-translate-x-full peer-disabled:cursor-not-allowed peer-disabled:opacity-60"></div>
</label>
</div>
</div>

View File

@@ -194,7 +194,7 @@ export default function TranscodeSettings() {
<div className="grid gap-6 md:grid-cols-2">
{/* Codec Selection */}
<div className="md:col-span-2 space-y-3">
<label className="text-xs font-bold text-helios-slate flex items-center gap-2">
<label className="text-xs font-medium text-helios-slate flex items-center gap-2">
<Video size={14} /> Preferred Codec
</label>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
@@ -239,7 +239,7 @@ export default function TranscodeSettings() {
{/* Quality Profile */}
<div className="md:col-span-2 space-y-3 pt-4">
<label className="text-xs font-bold text-helios-slate flex items-center gap-2">
<label className="text-xs font-medium text-helios-slate flex items-center gap-2">
<Gauge size={14} /> Quality Profile
</label>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
@@ -262,7 +262,7 @@ export default function TranscodeSettings() {
<div className="md:col-span-2 flex items-center justify-between rounded-lg border border-helios-line/20 bg-helios-surface-soft/60 p-4">
<div>
<p className="text-xs font-bold text-helios-slate">Allow Fallback</p>
<p className="text-xs font-medium text-helios-slate">Allow Fallback</p>
<p className="text-xs text-helios-slate mt-1">If preferred codec is unavailable, use the best available fallback.</p>
</div>
<div className="relative inline-flex items-center cursor-pointer">
@@ -273,12 +273,12 @@ export default function TranscodeSettings() {
onChange={(e) => setSettings({ ...settings, allow_fallback: e.target.checked })}
className="sr-only peer"
/>
<div className="w-10 h-5 bg-helios-line/20 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-helios-solar"></div>
<div className="w-10 h-5 bg-helios-line/20 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-helios-ink after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-helios-ink after:border-helios-line/30 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-helios-solar"></div>
</div>
</div>
<div className="md:col-span-2 space-y-3 pt-2">
<label className="text-xs font-bold text-helios-slate flex items-center gap-2">
<label className="text-xs font-medium text-helios-slate flex items-center gap-2">
<Film size={14} /> Subtitle Handling
</label>
<select
@@ -300,13 +300,13 @@ export default function TranscodeSettings() {
</div>
<div className="md:col-span-2 space-y-4 pt-2">
<label className="text-xs font-bold text-helios-slate flex items-center gap-2">
<label className="text-xs font-medium text-helios-slate flex items-center gap-2">
<Film size={14} /> Stream Rules
</label>
<div className="flex items-center justify-between rounded-lg border border-helios-line/20 bg-helios-surface-soft/60 p-4">
<div>
<p className="text-xs font-bold text-helios-slate">Strip commentary tracks</p>
<p className="text-xs font-medium text-helios-slate">Strip commentary tracks</p>
<p className="text-xs text-helios-slate mt-1">Adds built-in title keywords for common commentary tracks.</p>
</div>
<div className="relative inline-flex items-center cursor-pointer">
@@ -343,12 +343,12 @@ export default function TranscodeSettings() {
}}
className="sr-only peer"
/>
<div className="w-10 h-5 bg-helios-line/20 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-helios-solar"></div>
<div className="w-10 h-5 bg-helios-line/20 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-helios-ink after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-helios-ink after:border-helios-line/30 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-helios-solar"></div>
</div>
</div>
<div className="space-y-3">
<label className="text-xs font-bold text-helios-slate">
<label className="text-xs font-medium text-helios-slate">
Strip Audio Tracks By Title Keyword
</label>
<input
@@ -368,7 +368,7 @@ export default function TranscodeSettings() {
</div>
<div className="space-y-3">
<label className="text-xs font-bold text-helios-slate">
<label className="text-xs font-medium text-helios-slate">
Keep Only These Audio Languages
</label>
<CommaSeparatedInput
@@ -387,7 +387,7 @@ export default function TranscodeSettings() {
<div className="flex items-center justify-between rounded-lg border border-helios-line/20 bg-helios-surface-soft/60 p-4">
<div>
<p className="text-xs font-bold text-helios-slate">Keep only default audio track</p>
<p className="text-xs font-medium text-helios-slate">Keep only default audio track</p>
<p className="text-xs text-helios-slate mt-1">Strip all audio tracks except the one marked as default by the source file.</p>
</div>
<div className="relative inline-flex items-center cursor-pointer">
@@ -402,14 +402,14 @@ export default function TranscodeSettings() {
}
className="sr-only peer"
/>
<div className="w-10 h-5 bg-helios-line/20 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-helios-solar"></div>
<div className="w-10 h-5 bg-helios-line/20 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-helios-ink after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-helios-ink after:border-helios-line/30 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-helios-solar"></div>
</div>
</div>
</div>
{/* HDR + Tonemapping */}
<div className="md:col-span-2 space-y-3 pt-2">
<label className="text-xs font-bold text-helios-slate flex items-center gap-2">
<label className="text-xs font-medium text-helios-slate flex items-center gap-2">
<Film size={14} /> HDR Handling
</label>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
@@ -443,7 +443,7 @@ export default function TranscodeSettings() {
{settings.hdr_mode === "tonemap" && (
<>
<div className="space-y-3">
<label className="text-xs font-bold text-helios-slate flex items-center gap-2">
<label className="text-xs font-medium text-helios-slate flex items-center gap-2">
<Gauge size={14} /> Tonemap Algorithm
</label>
<select
@@ -460,7 +460,7 @@ export default function TranscodeSettings() {
</div>
<div className="space-y-3">
<label className="text-xs font-bold text-helios-slate flex items-center gap-2">
<label className="text-xs font-medium text-helios-slate flex items-center gap-2">
<Scale size={14} /> Tonemap Peak (nits)
</label>
<input
@@ -475,7 +475,7 @@ export default function TranscodeSettings() {
</div>
<div className="space-y-3">
<label className="text-xs font-bold text-helios-slate flex items-center gap-2">
<label className="text-xs font-medium text-helios-slate flex items-center gap-2">
<Zap size={14} /> Tonemap Desaturation
</label>
<input
@@ -494,7 +494,7 @@ export default function TranscodeSettings() {
{/* Numeric Inputs */}
<div className="space-y-3">
<label className="text-xs font-bold text-helios-slate flex items-center gap-2">
<label className="text-xs font-medium text-helios-slate flex items-center gap-2">
<Cpu size={14} /> Encoding Threads (libsvtav1/x265)
</label>
<input
@@ -508,7 +508,7 @@ export default function TranscodeSettings() {
</div>
<div className="space-y-3">
<label className="text-xs font-bold text-helios-slate flex items-center gap-2">
<label className="text-xs font-medium text-helios-slate flex items-center gap-2">
<Zap size={14} /> Concurrent Jobs
</label>
<input
@@ -523,7 +523,7 @@ export default function TranscodeSettings() {
</div>
<div className="space-y-3">
<label className="text-xs font-bold text-helios-slate flex items-center gap-2">
<label className="text-xs font-medium text-helios-slate flex items-center gap-2">
<Scale size={14} /> Min. Reduction (%)
</label>
<input
@@ -539,7 +539,7 @@ export default function TranscodeSettings() {
</div>
<div className="space-y-3">
<label className="text-xs font-bold text-helios-slate flex items-center gap-2">
<label className="text-xs font-medium text-helios-slate flex items-center gap-2">
<Film size={14} /> Min. File Size (MB)
</label>
<input

View File

@@ -16,20 +16,20 @@ const { error } = Astro.props;
<AlertTriangle className="w-8 h-8 text-helios-red" />
</div>
<h1 class="text-3xl font-bold text-white mb-3">500 Server Error</h1>
<h1 class="text-3xl font-bold text-helios-ink mb-3">500 Server Error</h1>
<p class="text-helios-text mb-8">
Alchemist encountered an internal error. Please check the backend logs.
</p>
{error instanceof Error ? (
<div class="bg-black/50 border border-white/5 rounded-lg p-4 mb-8 w-full overflow-auto text-left">
<div class="bg-black/50 border border-helios-line/10 rounded-lg p-4 mb-8 w-full overflow-auto text-left">
<p class="text-helios-red/90 font-mono text-sm break-words">{error.message}</p>
</div>
) : null}
<a
href="/"
class="px-6 py-2.5 bg-helios-orange hover:bg-helios-orange/90 text-white font-medium rounded-md transition-colors"
class="px-6 py-2.5 bg-helios-orange hover:bg-helios-orange/90 text-helios-main font-medium rounded-md transition-colors"
>
Return to Dashboard
</a>