release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m6s
release / go (push) Successful in 1m29s
release / integration (push) Successful in 4m30s
release / android (push) Successful in 5m18s
release / Build signed APK (releases and dev) (push) Successful in 5m32s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
Raw error red fails AA as text even with no tint behind it: in dark mode it measures 3.63:1 on obsidian, 3.23 on iron and 2.64 on slate, against 4.5. error-fg (the house formula, 50% toward parchment) measures 5.30 at worst across both modes. - All 19 text-error uses become text-error-fg: the "Couldn't load" messages on the admin pages, the integrations form errors, the flag popover, and the error toast's text. The toast keeps its error border, since a border is a graphic with a 3:1 floor. - check-tint-contrast flags raw error text anywhere (text-error, class:text-error, color: var(--fs-error)) and leaves borders and outlines alone. Run against the files before the swap, it finds all 19. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
103 lines
3.3 KiB
Svelte
103 lines
3.3 KiB
Svelte
<script lang="ts">
|
|
import { untrack } from 'svelte';
|
|
import { Flag } from 'lucide-svelte';
|
|
import { useQueryClient } from '@tanstack/svelte-query';
|
|
import { flagTrack } from '$lib/api/quarantine';
|
|
import { errCode } from '$lib/api/errors';
|
|
import { qk } from '$lib/api/queries';
|
|
import type { TrackRef, LidarrQuarantineReason } from '$lib/api/types';
|
|
|
|
let {
|
|
track,
|
|
onClose,
|
|
initialReason,
|
|
initialNotes
|
|
}: {
|
|
track: TrackRef;
|
|
onClose: () => void;
|
|
initialReason?: LidarrQuarantineReason;
|
|
initialNotes?: string;
|
|
} = $props();
|
|
|
|
let reason: LidarrQuarantineReason = $state(untrack(() => initialReason ?? 'bad_rip'));
|
|
let notes = $state(untrack(() => initialNotes ?? ''));
|
|
let submitting = $state(false);
|
|
let error = $state<string | null>(null);
|
|
|
|
const isUpdate = $derived(!!initialReason);
|
|
const client = useQueryClient();
|
|
|
|
async function submit() {
|
|
submitting = true;
|
|
error = null;
|
|
try {
|
|
await flagTrack({ track_id: track.id, reason, notes: notes.trim() || undefined });
|
|
await client.invalidateQueries({ queryKey: qk.myQuarantine() });
|
|
onClose();
|
|
} catch (e) {
|
|
const code = errCode(e);
|
|
error = code === 'unknown' ? 'flag_failed' : code;
|
|
} finally {
|
|
submitting = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div
|
|
role="dialog"
|
|
tabindex="-1"
|
|
aria-modal="false"
|
|
aria-labelledby="flag-popover-title"
|
|
class="absolute right-0 z-30 mt-1 w-72 rounded-lg border border-border bg-surface p-3 shadow-xl"
|
|
onclick={(e) => e.stopPropagation()}
|
|
onkeydown={(e) => { e.stopPropagation(); if (e.key === 'Escape') onClose(); }}
|
|
>
|
|
<h4 id="flag-popover-title" class="text-sm font-medium text-text-primary">
|
|
Flag this track as broken
|
|
</h4>
|
|
<label class="mt-2 block">
|
|
<span class="block text-xs text-text-secondary">Reason</span>
|
|
<select
|
|
bind:value={reason}
|
|
class="mt-1 w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent"
|
|
>
|
|
<option value="bad_rip">Bad rip</option>
|
|
<option value="wrong_file">Wrong file</option>
|
|
<option value="wrong_tags">Wrong tags</option>
|
|
<option value="duplicate">Duplicate</option>
|
|
<option value="other">Other</option>
|
|
</select>
|
|
</label>
|
|
<label class="mt-2 block">
|
|
<span class="block text-xs text-text-secondary">Notes (optional)</span>
|
|
<textarea
|
|
bind:value={notes}
|
|
maxlength="200"
|
|
placeholder="What's wrong with it?"
|
|
class="mt-1 w-full rounded-md border border-border bg-background px-2 py-1.5 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
|
|
rows="2"
|
|
></textarea>
|
|
</label>
|
|
{#if error}
|
|
<p class="mt-2 text-xs text-error-fg">Couldn't save flag — {error}</p>
|
|
{/if}
|
|
<div class="mt-3 flex justify-end gap-2">
|
|
<button
|
|
type="button"
|
|
class="rounded-md border border-border px-2.5 py-1 text-sm text-text-secondary hover:text-text-primary"
|
|
onclick={onClose}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onclick={submit}
|
|
disabled={submitting}
|
|
class="inline-flex items-center gap-1 rounded-md bg-action-secondary px-2.5 py-1 text-sm text-action-fg disabled:opacity-50"
|
|
>
|
|
<Flag size={14} strokeWidth={1} />
|
|
{isUpdate ? 'Update flag' : 'Flag'}
|
|
</button>
|
|
</div>
|
|
</div>
|