Fix banned CSS token patterns in frontend components

This commit is contained in:
2026-03-28 10:17:00 -04:00
parent 413f1e28e4
commit 12c4161a3e
4 changed files with 118 additions and 23 deletions

95
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,95 @@
# Copilot Instructions
Alchemist is a self-hosted media transcoding pipeline. It scans media libraries, analyzes video files, and intelligently encodes them using hardware acceleration (NVIDIA NVENC, Intel QSV, AMD VAAPI/AMF, Apple VideoToolbox) with CPU fallback.
**Stack:** Rust (Axum + SQLite/sqlx + tokio) backend, Astro 5 + React 18 + TypeScript frontend, Bun package manager.
## Commands
All tasks use `just` (install: `cargo install just`).
```bash
# Development
just dev # Backend (watch) + frontend dev server
just run # Backend only
just web # Frontend only
# Build
just build # Full production build
just check # All checks (mirrors CI): fmt + clippy + typecheck + build
# Tests
just test # All Rust tests
just test-filter <pattern> # Single test (e.g., just test-filter stream_rules)
just test-e2e # Playwright e2e tests
# Database
just db-reset # Wipe dev database
just db-reset-all # Wipe database AND config (triggers setup wizard)
```
Integration tests require FFmpeg/FFprobe installed locally.
## Architecture
### Backend (`src/`)
Central `AppState` holds SQLite pool, config, and broadcast channels, passed to Axum handlers.
- **`server/`** — HTTP routes, SSE events, auth (Argon2 + sessions), rate limiting, static assets
- **`db.rs`** — All SQLite queries via sqlx (no ORM), migration runner
- **`config.rs`** — TOML config structs
- **`media/`** — Core pipeline:
- `scanner.rs``analyzer.rs``planner.rs``pipeline.rs``processor.rs`
- `ffmpeg/` — Command builder with platform-specific encoder modules
- **`orchestrator.rs`** — FFmpeg process spawning and progress streaming
- **`system/`** — Hardware detection, file watcher, library scanner
- **`scheduler.rs`** — Off-peak cron scheduling
- **`notifications.rs`** — Discord, Gotify, Webhook integrations
### Frontend (`web/src/`)
Astro pages with React islands. UI reflects backend state via SSE — avoid optimistic UI unless reconciled with backend truth.
## Key Constraints
These are binding project rules:
- **Never overwrite user media by default** — always prefer reversible actions
- **Schema changes are additive only** — no renames, no drops; DBs from v0.2.5+ must remain usable
- **Cross-platform** — all core features must work on macOS, Linux, and Windows
- **Fail safe** — no data loss on failure; explicit error handling over implicit fallbacks
## Conventions
- **Error handling:** `anyhow` for application errors, `thiserror` for library errors
- **Logging:** `tracing` crate
- **Frontend icons:** `lucide-react`
- **Animations:** `framer-motion`
- **API calls:** Custom `apiFetch` utility from `web/src/lib/api`
- **Promise handling in React:** Use `void` prefix for async calls in useEffect
## Environment Variables
```
ALCHEMIST_CONFIG_PATH # Config file (default: ~/.config/alchemist/config.toml)
ALCHEMIST_DB_PATH # Database (default: ~/.config/alchemist/alchemist.db)
RUST_LOG # Log level (e.g., info, alchemist=debug)
```
## MCP Servers
For browser automation and e2e test development, add the Playwright MCP server to your configuration:
```json
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}
```
This enables Copilot to interact with the browser for debugging UI issues and developing e2e tests in `web-e2e/`.

View File

@@ -169,7 +169,7 @@ export default function SavingsOverview() {
title={`${entry.label}: ${entry.gb_saved.toFixed(1)} GB saved`}
/>
</div>
<div className="mt-2 truncate text-center text-[10px] text-helios-slate">
<div className="mt-2 truncate text-center text-xs text-helios-slate">
{entry.label}
</div>
</div>

View File

@@ -121,7 +121,7 @@ export default function ServerDirectoryPicker({
<div className="border-b border-helios-line/20 px-6 py-5 flex items-start justify-between gap-4">
<div>
<div className="flex items-center gap-3">
<div className="rounded-xl bg-helios-solar/10 p-2 text-helios-solar">
<div className="rounded-lg bg-helios-solar/10 p-2 text-helios-solar">
<FolderOpen size={20} />
</div>
<div>
@@ -129,14 +129,14 @@ export default function ServerDirectoryPicker({
<p className="text-sm text-helios-slate">{description}</p>
</div>
</div>
<p className="mt-3 text-[11px] text-helios-slate">
<p className="mt-3 text-xs text-helios-slate">
You are browsing the <span className="font-bold text-helios-ink">server filesystem</span>, not your browsers local machine.
</p>
</div>
<button
type="button"
onClick={onClose}
className="rounded-xl border border-helios-line/20 px-4 py-2 text-sm font-semibold text-helios-slate hover:bg-helios-surface-soft"
className="rounded-lg border border-helios-line/20 px-4 py-2 text-sm font-semibold text-helios-slate hover:bg-helios-surface-soft"
>
Close
</button>
@@ -145,7 +145,7 @@ export default function ServerDirectoryPicker({
<div className="grid grid-cols-1 lg:grid-cols-[320px_1fr] min-h-[620px]">
<aside className="border-r border-helios-line/20 bg-helios-surface-soft/40 px-5 py-5 space-y-5">
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-helios-slate">
<label className="text-xs font-medium text-helios-slate">
Jump To Server Path
</label>
<div className="flex gap-2">
@@ -154,12 +154,12 @@ export default function ServerDirectoryPicker({
value={manualPath}
onChange={(e) => setManualPath(e.target.value)}
placeholder="/media/movies"
className="flex-1 rounded-xl border border-helios-line/20 bg-helios-surface px-3 py-2 font-mono text-sm text-helios-ink focus:border-helios-solar focus:ring-1 focus:ring-helios-solar outline-none"
className="flex-1 rounded-lg border border-helios-line/20 bg-helios-surface px-3 py-2 font-mono text-sm text-helios-ink focus:border-helios-solar focus:ring-1 focus:ring-helios-solar outline-none"
/>
<button
type="button"
onClick={() => void loadBrowse(manualPath)}
className="rounded-xl bg-helios-solar px-4 py-2 text-sm font-semibold text-helios-main"
className="rounded-lg bg-helios-solar px-4 py-2 text-sm font-semibold text-helios-main"
>
Open
</button>
@@ -167,7 +167,7 @@ export default function ServerDirectoryPicker({
</div>
<div className="space-y-3">
<div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-helios-slate">
<div className="flex items-center gap-2 text-xs font-medium text-helios-slate">
<Sparkles size={12} />
Recommended Media Roots
</div>
@@ -182,13 +182,13 @@ export default function ServerDirectoryPicker({
<div className="flex items-start justify-between gap-3">
<div>
<div className="text-sm font-semibold text-helios-ink">{recommendation.label}</div>
<div className="text-[10px] text-helios-slate mt-1 break-all">{recommendation.path}</div>
<div className="text-xs text-helios-slate mt-1 break-all">{recommendation.path}</div>
</div>
<span className={`rounded-full border px-2 py-1 text-[10px] font-bold uppercase tracking-wider ${mediaBadgeTone(recommendation.media_hint)}`}>
<span className={`rounded-full border px-2 py-1 text-xs font-medium ${mediaBadgeTone(recommendation.media_hint)}`}>
{recommendation.media_hint}
</span>
</div>
<p className="mt-2 text-[11px] text-helios-slate">{recommendation.reason}</p>
<p className="mt-2 text-xs text-helios-slate">{recommendation.reason}</p>
</button>
))}
</div>
@@ -220,7 +220,7 @@ export default function ServerDirectoryPicker({
<div className="mb-4 rounded-lg border border-helios-line/20 bg-helios-surface-soft/40 px-4 py-4">
<div className="flex items-start justify-between gap-4">
<div>
<p className="text-[10px] font-bold uppercase tracking-widest text-helios-slate">
<p className="text-xs font-medium text-helios-slate">
Current Server Folder
</p>
<p className="mt-2 font-mono text-sm text-helios-ink break-all">
@@ -230,7 +230,7 @@ export default function ServerDirectoryPicker({
<button
type="button"
onClick={() => onSelect(browse.path)}
className="shrink-0 rounded-xl bg-helios-solar px-4 py-2 text-sm font-semibold text-helios-main"
className="shrink-0 rounded-lg bg-helios-solar px-4 py-2 text-sm font-semibold text-helios-main"
>
Use This Folder
</button>
@@ -240,7 +240,7 @@ export default function ServerDirectoryPicker({
{browse.warnings.map((warning) => (
<div
key={warning}
className="flex items-start gap-2 rounded-xl border border-amber-500/20 bg-amber-500/10 px-3 py-2 text-xs text-amber-500"
className="flex items-start gap-2 rounded-lg border border-amber-500/20 bg-amber-500/10 px-3 py-2 text-xs text-amber-500"
>
<AlertTriangle size={14} className="mt-0.5 shrink-0" />
<span>{warning}</span>
@@ -251,7 +251,7 @@ export default function ServerDirectoryPicker({
</div>
<div className="flex items-center justify-between mb-3">
<div className="text-[10px] font-bold uppercase tracking-widest text-helios-slate">
<div className="text-xs font-medium text-helios-slate">
Child Folders
</div>
{loading && <div className="text-xs text-helios-slate animate-pulse">Loading</div>}
@@ -275,26 +275,26 @@ export default function ServerDirectoryPicker({
className="min-w-0 flex-1 text-left"
>
<div className="flex items-center gap-3">
<div className="rounded-xl bg-helios-solar/10 p-2 text-helios-solar">
<div className="rounded-lg bg-helios-solar/10 p-2 text-helios-solar">
<Folder size={16} />
</div>
<div className="min-w-0">
<div className="flex items-center gap-2">
<span className="truncate text-sm font-semibold text-helios-ink">{entry.name}</span>
<span className={`rounded-full border px-2 py-1 text-[10px] font-bold uppercase tracking-wider ${mediaBadgeTone(entry.media_hint)}`}>
<span className={`rounded-full border px-2 py-1 text-xs font-medium ${mediaBadgeTone(entry.media_hint)}`}>
{entry.media_hint}
</span>
{entry.hidden && (
<span className="rounded-full border border-helios-line/20 px-2 py-1 text-[10px] font-bold uppercase tracking-wider text-helios-slate">
<span className="rounded-full border border-helios-line/20 px-2 py-1 text-xs font-medium text-helios-slate">
hidden
</span>
)}
</div>
<div className="mt-1 break-all font-mono text-[11px] text-helios-slate">
<div className="mt-1 break-all font-mono text-xs text-helios-slate">
{entry.path}
</div>
{entry.warning && (
<div className="mt-2 text-[11px] text-amber-500">{entry.warning}</div>
<div className="mt-2 text-xs text-amber-500">{entry.warning}</div>
)}
</div>
</div>
@@ -302,7 +302,7 @@ export default function ServerDirectoryPicker({
<button
type="button"
onClick={() => onSelect(entry.path)}
className="shrink-0 rounded-xl border border-helios-line/20 px-3 py-2 text-xs font-semibold text-helios-ink hover:border-helios-solar/30"
className="shrink-0 rounded-lg border border-helios-line/20 px-3 py-2 text-xs font-semibold text-helios-ink hover:border-helios-solar/30"
>
Select
</button>

View File

@@ -84,13 +84,13 @@ export default function ToastRegion() {
<div
key={toast.id}
role={toast.kind === "error" ? "alert" : "status"}
className={`pointer-events-auto rounded-xl border p-3 shadow-xl ${className}`}
className={`pointer-events-auto rounded-lg border p-3 shadow-xl ${className}`}
>
<div className="flex items-start gap-2">
<Icon size={16} />
<div className="min-w-0 flex-1">
{toast.title && (
<p className="text-xs font-bold uppercase tracking-wide">{toast.title}</p>
<p className="text-xs font-medium">{toast.title}</p>
)}
<p className="text-sm break-words">{toast.message}</p>
</div>