mirror of
https://github.com/bybrooklyn/alchemist.git
synced 2026-04-18 01:43:34 -04:00
Stability changes have been updated.
This commit is contained in:
208
CHANGELOG.md
208
CHANGELOG.md
@@ -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.
|
||||
|
||||
205
backlog.md
205
backlog.md
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
1
docs/static/img/README.md
vendored
1
docs/static/img/README.md
vendored
@@ -1,4 +1,3 @@
|
||||
# Images
|
||||
|
||||
- `favicon.ico` — site favicon (replace with Alchemist favicon)
|
||||
- `social-card.png` — OG social card (add before launch)
|
||||
|
||||
BIN
docs/static/img/favicon.ico
vendored
BIN
docs/static/img/favicon.ico
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 85 KiB |
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 })}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user