Improve operational UX, recovery flows, and action safety
Implement the approved UX review across Fleet, Betas, Library, deployments, and Settings. Add reviewed promotion and reconciliation plans, atomic queued-removal cancellation, recoverable beta drafts and launch inputs, accessible controls, and mobile improvements. Validated with 473 tests, a clean typecheck, production build, and isolated desktop/mobile browser walkthroughs. Include the review and implementation coverage notes.
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { GithubPluginRelease, PluginUpdate } from '$lib/plugin-github.js';
|
||||
import Card from './primitives/Card.svelte';
|
||||
import Button from './primitives/Button.svelte';
|
||||
import Badge from './primitives/Badge.svelte';
|
||||
import { toast } from '$ui/toast.js';
|
||||
|
||||
let { pluginId, targetPath, disabled = false, onimport }: {
|
||||
pluginId?: number; targetPath?: string; disabled?: boolean; onimport?: () => void | Promise<void>;
|
||||
let { pluginId, targetPath, disabled = false, busy = $bindable(false), onimport }: {
|
||||
pluginId?: number; targetPath?: string; disabled?: boolean; busy?: boolean; onimport?: (result: { pluginId: number; betaUrl: string; alreadyImported: boolean }) => void | Promise<void>;
|
||||
} = $props();
|
||||
let url = $state('');
|
||||
let name = $state('');
|
||||
@@ -15,10 +16,11 @@
|
||||
let release = $state<GithubPluginRelease | null>(null);
|
||||
let assetId = $state('');
|
||||
let update = $state<PluginUpdate | null>(null);
|
||||
let busy = $state(false);
|
||||
let checking = $state(false);
|
||||
let error = $state('');
|
||||
let success = $state<{ pluginId: number; betaUrl: string; sha256: string; alreadyImported: boolean } | null>(null);
|
||||
let active = true;
|
||||
onDestroy(() => { active = false; });
|
||||
|
||||
function showRelease(value: GithubPluginRelease) {
|
||||
release = value;
|
||||
@@ -58,6 +60,7 @@
|
||||
|
||||
async function importRelease(startBeta: boolean) {
|
||||
if (!release || !assetId || busy || disabled) return;
|
||||
const startPath = window.location.pathname;
|
||||
busy = true; error = ''; success = null;
|
||||
try {
|
||||
const response = await fetch('/api/plugins/github/import', {
|
||||
@@ -67,11 +70,12 @@
|
||||
});
|
||||
const body = await response.json();
|
||||
if (!response.ok) throw new Error(body.error ?? 'Import failed.');
|
||||
if (!active) { toast('Plugin imported. Review its version when ready.', { variant: 'success', href: `/plugins/${body.pluginId}` }); return; }
|
||||
success = body;
|
||||
await onimport?.();
|
||||
if (startBeta) await goto(body.betaUrl);
|
||||
await onimport?.(body);
|
||||
if (startBeta && window.location.pathname === startPath) await goto(body.betaUrl);
|
||||
else if (pluginId) await checkUpdates();
|
||||
} catch (e) { error = (e as Error).message; }
|
||||
} catch (e) { error = (e as Error).message; if (!active) toast(`Plugin import failed: ${error}`, { variant: 'danger', href: '/plugins' }); }
|
||||
finally { busy = false; }
|
||||
}
|
||||
</script>
|
||||
@@ -172,4 +176,5 @@
|
||||
.success { display: block; padding: var(--space-3); background: var(--surface-sunken); border-radius: var(--radius-md); font-size: var(--text-sm); }
|
||||
.success strong, .checksum { display: block; margin-bottom: var(--space-2); }
|
||||
.checksum { overflow-wrap: anywhere; font: var(--text-xs) var(--font-mono); margin-top: var(--space-2); }
|
||||
input:not([type='checkbox']):not([type='file']), select { border-color: var(--border-control); }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user