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,8 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { changeConfigValue, configLines, configWarnings } from '$lib/config-workbench.js';
|
||||
import { addConfigSetting, changeConfigValue, configLines, configWarnings } from '$lib/config-workbench.js';
|
||||
import Button from './primitives/Button.svelte';
|
||||
let { text = $bindable(''), overlay = false, disabled = false }: { text?: string; overlay?: boolean; disabled?: boolean } = $props();
|
||||
let mode = $state<'raw' | 'settings'>('raw');
|
||||
let search = $state('');
|
||||
let newKey = $state('');
|
||||
let newValue = $state('');
|
||||
let addError = $state('');
|
||||
function addSetting() {
|
||||
try { text = addConfigSetting(text, newKey, newValue); search = ''; newKey = ''; newValue = ''; addError = ''; }
|
||||
catch (error) { addError = (error as Error).message; }
|
||||
}
|
||||
function addOnEnter(event: KeyboardEvent) { if (event.key === 'Enter') { event.preventDefault(); if (!disabled && newKey.trim() && newValue.trim()) addSetting(); } }
|
||||
const entries = $derived(configLines(text));
|
||||
const visible = $derived(entries.filter(e => `${e.key} ${e.value}`.toLowerCase().includes(search.toLowerCase())));
|
||||
const warnings = $derived(configWarnings(text, overlay));
|
||||
@@ -26,12 +35,14 @@
|
||||
<label class="setting"><span><code>{entry.key}</code><small>Line {entry.index + 1}</small></span>
|
||||
<input value={entry.value} {disabled} aria-label={`${entry.key}, line ${entry.index + 1}`} oninput={e => text = changeConfigValue(text, entry.index, e.currentTarget.value)} />
|
||||
</label>
|
||||
{:else}<p class="hint">{entries.length ? 'No matching settings.' : 'Add set KEY VALUE lines in the raw editor to get started.'}</p>{/each}
|
||||
{:else}<p class="hint">{entries.length ? 'No matching settings.' : 'Add your first setting below.'}</p>{/each}
|
||||
<div class="add-setting"><label>Setting name<input bind:value={newKey} onkeydown={addOnEnter} placeholder="g_inactivity" {disabled} /></label><label>Value<input bind:value={newValue} onkeydown={addOnEnter} placeholder="0" {disabled} /></label><Button size="sm" onclick={addSetting} disabled={disabled || !newKey.trim() || !newValue.trim()}>Add setting</Button></div>
|
||||
{#if addError}<p class="warnings" role="alert">{addError}</p>{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if warnings.length}
|
||||
<details class="warnings"><summary>{warnings.length} configuration {warnings.length === 1 ? 'note' : 'notes'}</summary>
|
||||
<details class="warnings" open={warnings.some(w => w.includes('ignored') || w.includes('no value'))}><summary>{warnings.length} configuration {warnings.length === 1 ? 'note' : 'notes'} — review before saving</summary>
|
||||
<ul>{#each warnings as warning}<li>{warning}</li>{/each}</ul>
|
||||
</details>
|
||||
{/if}
|
||||
@@ -50,6 +61,8 @@
|
||||
input { box-sizing: border-box; background: var(--surface); color: var(--text-primary); border: 1px solid var(--border-strong); border-radius: var(--radius-sm); padding: 9px 12px; font: var(--text-sm) var(--font-mono); width: 100%; }
|
||||
.setting { display: grid; grid-template-columns: minmax(140px, 1fr) minmax(0, 1.4fr); align-items: center; gap: 16px; padding: 12px 0; border-top: 1px solid var(--border-subtle); }
|
||||
.setting span { min-width: 0; overflow-wrap: anywhere; }
|
||||
.add-setting { display: flex; align-items: end; flex-wrap: wrap; gap: 12px; border-top: 1px solid var(--border-strong); padding-top: 16px; }
|
||||
.add-setting label { flex: 1 1 140px; display: grid; gap: 6px; font-size: var(--text-sm); }
|
||||
code { font: var(--text-sm) var(--font-mono); }
|
||||
small { display: block; color: var(--text-secondary); font-size: var(--text-xs); margin-top: 4px; }
|
||||
.hint { color: var(--text-secondary); font-size: var(--text-xs); }
|
||||
@@ -57,4 +70,5 @@
|
||||
summary { cursor: pointer; }
|
||||
li { margin: 6px 0; }
|
||||
@media (max-width: 600px) { .setting { grid-template-columns: 1fr; gap: 8px; } .toolbar > .hint { display: none; } }
|
||||
input:not([type='checkbox']):not([type='file']), textarea { border-color: var(--border-control); }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user