1
0
forked from wrenn/wrenn

Minor UI copy updates across capsules and templates pages

This commit is contained in:
2026-03-26 03:58:12 +06:00
parent 139f86bf9c
commit 12d1e356fa
5 changed files with 51 additions and 33 deletions

View File

@ -56,6 +56,7 @@
class="w-full rounded-[var(--radius-input)] border border-[var(--color-border)] bg-[var(--color-bg-4)] px-3 py-2 font-mono text-ui text-[var(--color-text-bright)] outline-none placeholder:text-[var(--color-text-muted)] transition-colors duration-150 focus:border-[var(--color-accent)]" class="w-full rounded-[var(--radius-input)] border border-[var(--color-border)] bg-[var(--color-bg-4)] px-3 py-2 font-mono text-ui text-[var(--color-text-bright)] outline-none placeholder:text-[var(--color-text-muted)] transition-colors duration-150 focus:border-[var(--color-accent)]"
placeholder="minimal" placeholder="minimal"
/> />
<p class="mt-1.5 text-meta text-[var(--color-text-muted)]">Name of a snapshot or base image to boot from.</p>
</div> </div>
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3">
@ -85,14 +86,16 @@
</div> </div>
<div> <div>
<label class="mb-1.5 block text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-tertiary)]" for="create-timeout">Idle timeout (seconds — 0 = never pause)</label> <label class="mb-1.5 block text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-tertiary)]" for="create-timeout">Idle timeout</label>
<input <input
id="create-timeout" id="create-timeout"
type="number" type="number"
min="0" min="0"
bind:value={createForm.timeout_sec} bind:value={createForm.timeout_sec}
class="w-full rounded-[var(--radius-input)] border border-[var(--color-border)] bg-[var(--color-bg-4)] px-3 py-2 font-mono text-ui text-[var(--color-text-bright)] outline-none transition-colors duration-150 focus:border-[var(--color-accent)]" class="w-full rounded-[var(--radius-input)] border border-[var(--color-border)] bg-[var(--color-bg-4)] px-3 py-2 font-mono text-ui text-[var(--color-text-bright)] outline-none transition-colors duration-150 focus:border-[var(--color-accent)]"
placeholder="0"
/> />
<p class="mt-1.5 text-meta text-[var(--color-text-muted)]">Seconds of inactivity before the capsule pauses. Set to 0 to keep it running indefinitely.</p>
</div> </div>
</div> </div>

View File

@ -47,7 +47,7 @@
Capsules Capsules
</h1> </h1>
<p class="mt-2 text-ui text-[var(--color-text-secondary)]"> <p class="mt-2 text-ui text-[var(--color-text-secondary)]">
Isolated VMs. Start cold in under a second — pause, snapshot, or destroy at will. All active and recent capsules across your team.
</p> </p>
</div> </div>

View File

@ -247,6 +247,13 @@
return `${Math.floor(seconds / 86400)}d ago`; return `${Math.floor(seconds / 86400)}d ago`;
} }
function fmtTimeout(sec: number): string {
if (!sec) return 'None';
if (sec < 60) return `${sec}s`;
if (sec < 3600) return `${Math.round(sec / 60)}m`;
return `${Math.round(sec / 3600)}h`;
}
function handleClickOutside(event: MouseEvent) { function handleClickOutside(event: MouseEvent) {
if (openMenuId && !(event.target as Element)?.closest('.status-menu-container')) { if (openMenuId && !(event.target as Element)?.closest('.status-menu-container')) {
openMenuId = null; openMenuId = null;
@ -300,7 +307,7 @@
class="w-full rounded-[var(--radius-input)] border border-[var(--color-border)] bg-[var(--color-bg-2)] py-2 pl-9 pr-3 font-mono text-ui text-[var(--color-text-bright)] outline-none placeholder:text-[var(--color-text-muted)] transition-colors duration-150 focus:border-[var(--color-accent)]" class="w-full rounded-[var(--radius-input)] border border-[var(--color-border)] bg-[var(--color-bg-2)] py-2 pl-9 pr-3 font-mono text-ui text-[var(--color-text-bright)] outline-none placeholder:text-[var(--color-text-muted)] transition-colors duration-150 focus:border-[var(--color-accent)]"
/> />
</div> </div>
<span class="text-ui text-[var(--color-text-secondary)]">{filteredCapsules.length} total</span> <span class="text-ui text-[var(--color-text-secondary)]">{filteredCapsules.length} capsule{filteredCapsules.length !== 1 ? 's' : ''}</span>
<div class="flex-1"></div> <div class="flex-1"></div>
@ -363,8 +370,11 @@
</div> </div>
{#if error} {#if error}
<div class="mb-4 rounded-[var(--radius-card)] border border-[var(--color-red)]/30 bg-[var(--color-red)]/5 px-4 py-3 text-ui text-[var(--color-red)]"> <div class="mb-4 flex items-start gap-3 rounded-[var(--radius-card)] border border-[var(--color-red)]/30 bg-[var(--color-red)]/5 px-4 py-3">
{error} <svg class="mt-0.5 shrink-0 text-[var(--color-red)]" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="12" /><line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
<span class="text-ui text-[var(--color-red)]">{error}. Try refreshing the page.</span>
</div> </div>
{/if} {/if}
@ -466,14 +476,14 @@
<!-- Idle Timeout --> <!-- Idle Timeout -->
<div class="px-5 py-4"> <div class="px-5 py-4">
<span class="font-mono text-ui text-[var(--color-text-secondary)]">{capsule.timeout_sec ? `${capsule.timeout_sec}s` : '—'}</span> <span class="font-mono text-ui text-[var(--color-text-secondary)]">{fmtTimeout(capsule.timeout_sec)}</span>
</div> </div>
<!-- Started --> <!-- Started -->
<div class="px-5 py-4"> <div class="px-5 py-4">
<span class="text-ui text-[var(--color-text-secondary)]" title={capsule.started_at ?? ''}>{formatTime(capsule.started_at)}</span> <span class="text-ui text-[var(--color-text-secondary)]" title={capsule.started_at ?? ''}>{formatTime(capsule.started_at)}</span>
{#if capsule.last_active_at} {#if capsule.last_active_at}
<span class="ml-1.5 text-label text-[var(--color-text-muted)]">{timeAgo(capsule.last_active_at)}</span> <span class="ml-1.5 text-label text-[var(--color-text-muted)]">active {timeAgo(capsule.last_active_at)}</span>
{/if} {/if}
</div> </div>
@ -612,7 +622,7 @@
<line x1="12" y1="9" x2="12" y2="13" /> <line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" /> <line x1="12" y1="17" x2="12.01" y2="17" />
</svg> </svg>
<p class="text-meta text-[var(--color-amber)] leading-relaxed">This capsule will be <strong class="font-semibold">paused first</strong> — memory state is captured at rest.</p> <p class="text-meta text-[var(--color-amber)] leading-relaxed">This capsule will be <strong class="font-semibold">paused first</strong>, then its full state (memory + disk) will be captured.</p>
</div> </div>
{:else} {:else}
<p class="text-ui text-[var(--color-text-tertiary)]">The capsule's current memory state will be captured and stored as a reusable snapshot.</p> <p class="text-ui text-[var(--color-text-tertiary)]">The capsule's current memory state will be captured and stored as a reusable snapshot.</p>

View File

@ -512,7 +512,7 @@
<svg class="shrink-0 text-[var(--color-red)]" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg class="shrink-0 text-[var(--color-red)]" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="12" /><line x1="12" y1="16" x2="12.01" y2="16" /> <circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="12" /><line x1="12" y1="16" x2="12.01" y2="16" />
</svg> </svg>
<span class="text-ui text-[var(--color-red)]">Failed to load metrics: {metricsError}</span> <span class="text-ui text-[var(--color-red)]">Could not load metrics: {metricsError}. Will retry automatically.</span>
</div> </div>
{/if} {/if}

View File

@ -114,15 +114,15 @@
} }
function emptyHeading(f: TypeFilter): string { function emptyHeading(f: TypeFilter): string {
if (f === 'snapshot') return 'No snapshots'; if (f === 'snapshot') return 'No snapshots yet';
if (f === 'base') return 'No images'; if (f === 'base') return 'No base images';
return 'No templates yet'; return 'No snapshots yet';
} }
function emptyDescription(f: TypeFilter): string { function emptyDescription(f: TypeFilter): string {
if (f === 'snapshot') return 'Pause a capsule from the Capsules page, then snapshot it to capture its state.'; if (f === 'snapshot') return 'Pause a running capsule, then choose Snapshot to save its state.';
if (f === 'base') return 'Base images are added by the Wrenn team. Contact support to request a custom image.'; if (f === 'base') return 'Base images are provided by the Wrenn team. Contact support to request a custom one.';
return 'To create a snapshot, go to Capsules, pause a running capsule, then choose Snapshot.'; return 'Pause a running capsule, then choose Snapshot to save its state. You can launch new capsules from any snapshot.';
} }
onMount(fetchSnapshots); onMount(fetchSnapshots);
@ -162,7 +162,7 @@
Templates Templates
</h1> </h1>
<p class="mt-2 text-ui text-[var(--color-text-secondary)]"> <p class="mt-2 text-ui text-[var(--color-text-secondary)]">
Snapshots capture a live capsule state. Base images are the rootfs every capsule starts from. Launch a full VM from any template. Snapshots capture a running capsule's state. Base images are the starting point for every new capsule. Launch from either.
</p> </p>
</div> </div>
</div> </div>
@ -206,8 +206,11 @@
{#if pageTab === 'snapshots'} {#if pageTab === 'snapshots'}
<div class="p-8" style="animation: fadeUp 0.35s ease both"> <div class="p-8" style="animation: fadeUp 0.35s ease both">
{#if error} {#if error}
<div class="mb-4 rounded-[var(--radius-card)] border border-[var(--color-red)]/30 bg-[var(--color-red)]/5 px-4 py-3 text-ui text-[var(--color-red)]"> <div class="mb-4 flex items-start gap-3 rounded-[var(--radius-card)] border border-[var(--color-red)]/30 bg-[var(--color-red)]/5 px-4 py-3">
{error} <svg class="mt-0.5 shrink-0 text-[var(--color-red)]" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="12" /><line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
<span class="text-ui text-[var(--color-red)]">{error}. Try refreshing the page.</span>
</div> </div>
{/if} {/if}
@ -274,11 +277,11 @@
</div> </div>
<span class="text-meta text-[var(--color-text-muted)]"> <span class="text-meta text-[var(--color-text-muted)]">
{filteredSnapshots.length} {filteredSnapshots.length}
{typeFilter === 'all' {typeFilter === 'snapshot'
? filteredSnapshots.length === 1 ? 'template' : 'templates' ? filteredSnapshots.length === 1 ? 'snapshot' : 'snapshots'
: typeFilter === 'snapshot' : typeFilter === 'base'
? filteredSnapshots.length === 1 ? 'snapshot' : 'snapshots' ? filteredSnapshots.length === 1 ? 'image' : 'images'
: filteredSnapshots.length === 1 ? 'image' : 'images'} : filteredSnapshots.length === 1 ? 'item' : 'total'}
</span> </span>
</div> </div>
@ -447,11 +450,11 @@
<p class="mt-3 text-meta text-[var(--color-text-muted)]"> <p class="mt-3 text-meta text-[var(--color-text-muted)]">
{filteredSnapshots.length} {filteredSnapshots.length}
{typeFilter === 'all' {typeFilter === 'snapshot'
? filteredSnapshots.length === 1 ? 'template' : 'templates' ? filteredSnapshots.length === 1 ? 'snapshot' : 'snapshots'
: typeFilter === 'snapshot' : typeFilter === 'base'
? filteredSnapshots.length === 1 ? 'snapshot' : 'snapshots' ? filteredSnapshots.length === 1 ? 'image' : 'images'
: filteredSnapshots.length === 1 ? 'image' : 'images'} : filteredSnapshots.length === 1 ? 'item' : 'total'}
{typeFilter !== 'all' ? '· filtered' : '· total'} {typeFilter !== 'all' ? '· filtered' : '· total'}
</p> </p>
{/if} {/if}
@ -513,10 +516,10 @@
class="relative w-full max-w-[380px] rounded-[var(--radius-card)] border border-[var(--color-border-mid)] bg-[var(--color-bg-2)] p-6" class="relative w-full max-w-[380px] rounded-[var(--radius-card)] border border-[var(--color-border-mid)] bg-[var(--color-bg-2)] p-6"
style="animation: fadeUp 0.2s ease both" style="animation: fadeUp 0.2s ease both"
> >
<h2 class="font-serif text-heading tracking-[-0.02em] text-[var(--color-text-bright)]">Delete Snapshot</h2> <h2 class="font-serif text-heading tracking-[-0.02em] text-[var(--color-text-bright)]">Delete snapshot</h2>
<p class="mt-2 text-ui text-[var(--color-text-tertiary)]"> <p class="mt-2 text-ui text-[var(--color-text-tertiary)]">
Permanently delete <span class="font-mono font-medium text-[var(--color-text-secondary)]">{deleteTarget.name}</span>. Permanently delete <span class="font-mono font-medium text-[var(--color-text-secondary)]">{deleteTarget.name}</span>.
Any capsule using this template will not be affected, but you won't be able to launch from it again. Running capsules won't be affected, but you won't be able to launch new ones from it.
</p> </p>
{#if deleteTarget.type === 'snapshot'} {#if deleteTarget.type === 'snapshot'}
@ -526,7 +529,7 @@
<line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" /> <line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" />
</svg> </svg>
<p class="text-meta leading-relaxed text-[var(--color-amber)]"> <p class="text-meta leading-relaxed text-[var(--color-amber)]">
This live capture includes saved memory state. Any capsule relying on it will be unable to resume. This snapshot includes memory state. Paused capsules that depend on it won't be able to resume.
</p> </p>
</div> </div>
{/if} {/if}
@ -580,7 +583,7 @@
> >
<h2 class="font-serif text-heading tracking-[-0.02em] text-[var(--color-text-bright)]">Launch Capsule</h2> <h2 class="font-serif text-heading tracking-[-0.02em] text-[var(--color-text-bright)]">Launch Capsule</h2>
<p class="mt-1 text-ui text-[var(--color-text-tertiary)]"> <p class="mt-1 text-ui text-[var(--color-text-tertiary)]">
Configure resources and launch. The VM will clone from this template and be ready in seconds. Configure resources and launch a new capsule from this snapshot.
</p> </p>
{#if launchError} {#if launchError}
@ -655,14 +658,16 @@
<!-- Timeout --> <!-- Timeout -->
<div class="mt-4"> <div class="mt-4">
<label class="mb-1.5 block text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-tertiary)]" for="launch-timeout">Auto-pause timeout (seconds, 0 = never)</label> <label class="mb-1.5 block text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-tertiary)]" for="launch-timeout">Idle timeout</label>
<input <input
id="launch-timeout" id="launch-timeout"
type="number" type="number"
min="0" min="0"
bind:value={launchTimeoutSec} bind:value={launchTimeoutSec}
placeholder="0"
class="w-full rounded-[var(--radius-input)] border border-[var(--color-border)] bg-[var(--color-bg-4)] px-3 py-2 font-mono text-ui text-[var(--color-text-bright)] outline-none transition-colors duration-150 focus:border-[var(--color-accent)]" class="w-full rounded-[var(--radius-input)] border border-[var(--color-border)] bg-[var(--color-bg-4)] px-3 py-2 font-mono text-ui text-[var(--color-text-bright)] outline-none transition-colors duration-150 focus:border-[var(--color-accent)]"
/> />
<p class="mt-1.5 text-meta text-[var(--color-text-muted)]">Seconds of inactivity before the capsule pauses. Set to 0 to keep it running indefinitely.</p>
</div> </div>
<div class="mt-6 flex justify-end gap-3"> <div class="mt-6 flex justify-end gap-3">