|
|
|
@@ -1,6 +1,6 @@
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
import { contextHref } from '$lib/navigation-context.js';
|
|
|
|
|
import { onDestroy, onMount, untrack } from 'svelte';
|
|
|
|
|
import { onDestroy, untrack } from 'svelte';
|
|
|
|
|
import { page } from '$app/stores';
|
|
|
|
|
import type { PageData } from './$types';
|
|
|
|
|
import type { ProvisioningDetail } from '$lib/provisioning-detail.js';
|
|
|
|
@@ -43,6 +43,8 @@
|
|
|
|
|
const finishedAt = $derived(timestampISO(deployment?.finished_at));
|
|
|
|
|
const canOverride = $derived(data.user?.role === 'admin' && detail?.canOverrideSafety);
|
|
|
|
|
const outcome = $derived(detail ? provisioningOutcome(detail) : null);
|
|
|
|
|
const active = $derived(outcome?.active ?? false);
|
|
|
|
|
const duration = $derived(finishedAt || active ? elapsedTime(startedAt, finishedAt ? Date.parse(finishedAt) : now) : 'Not recorded');
|
|
|
|
|
const serverHref = $derived(deployment && deployment.server_exists && !deployment.server_deleted_at ? `/servers/${encodeURIComponent(deployment.server)}` : null);
|
|
|
|
|
const recipeStatus = $derived(detail?.progress?.recipeStatus ?? null);
|
|
|
|
|
const recipeHeading = $derived(recipeStatus === 'queued' ? 'Setup captured when queued' : recipeStatus === 'attempted' ? 'Files selected for this attempt' : 'Recorded launch files');
|
|
|
|
@@ -55,7 +57,10 @@
|
|
|
|
|
controller.setOperation(snapshot.deployment.id, snapshot, snapshot.generatedAt);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
onMount(() => pollVisible(() => { now = Date.now(); return controller.refresh(); }, 5000, false));
|
|
|
|
|
$effect(() => {
|
|
|
|
|
if (!active) return;
|
|
|
|
|
return pollVisible(() => { now = Date.now(); return controller.refresh(); }, 5000, false);
|
|
|
|
|
});
|
|
|
|
|
onDestroy(() => controller.dispose());
|
|
|
|
|
async function retryWithRebuild() {
|
|
|
|
|
if (!deployment || !detail?.canRetryWithRebuild || view.busy) return;
|
|
|
|
@@ -95,31 +100,28 @@
|
|
|
|
|
<svelte:head><title>{deployment ? `${actionLabels[deployment.action]} ${deployment.server}` : 'Provisioning'} | Electric Cherry</title></svelte:head>
|
|
|
|
|
|
|
|
|
|
<div class="page">
|
|
|
|
|
<SectionHeader title={deployment ? `${actionLabels[deployment.action]} · ${serverTitle(deployment.server)}` : 'Provisioning operation'} subtitle={deployment?.server}>
|
|
|
|
|
{#snippet crumbs()}<Breadcrumbs items={[{ label: 'Provisioning', href: '/provisioning' }, { label: 'Operation' }]} />{/snippet}
|
|
|
|
|
{#snippet actions()}{#if serverHref}<Button href={serverHref}>View server</Button>{/if}{/snippet}
|
|
|
|
|
</SectionHeader>
|
|
|
|
|
{#if deployment && outcome}
|
|
|
|
|
<div class="operation-summary">
|
|
|
|
|
<p>
|
|
|
|
|
<div class="operation-header">
|
|
|
|
|
<SectionHeader title={deployment ? `${actionLabels[deployment.action]} · ${serverTitle(deployment.server)}` : 'Provisioning operation'} subtitle={deployment?.server}>
|
|
|
|
|
{#snippet crumbs()}<Breadcrumbs items={[{ label: 'Provisioning', href: '/provisioning' }, { label: 'Operation' }]} />{/snippet}
|
|
|
|
|
{#snippet actions()}{#if serverHref}<Button href={serverHref}>View server</Button>{/if}{/snippet}
|
|
|
|
|
</SectionHeader>
|
|
|
|
|
{#if deployment && outcome}
|
|
|
|
|
<p class="operation-summary">
|
|
|
|
|
<span>Requested by {provisioningRequester(deployment)}</span>{' · '}
|
|
|
|
|
<span>Started {#if startedAt}<time datetime={startedAt}>{$timeFormat.dateTime(startedAt)}</time>{:else}Not recorded{/if}</span>{' · '}
|
|
|
|
|
<span>{#if finishedAt}Finished <time datetime={finishedAt}>{$timeFormat.dateTime(finishedAt)}</time>{:else}{outcome.active ? 'In progress' : 'Finished: Not recorded'}{/if}</span>
|
|
|
|
|
<span>{#if startedAt}<time datetime={startedAt} title="Operation started">{$timeFormat.dateTime(startedAt)}</time>{:else}Start: Not recorded{/if}</span>{' · '}
|
|
|
|
|
<span title={finishedAt ? `Finished ${$timeFormat.dateTime(finishedAt)}` : undefined}>{finishedAt ? `took ${duration}` : active ? `${duration} elapsed` : 'Duration: Not recorded'}</span>
|
|
|
|
|
</p>
|
|
|
|
|
<p class="timing-note">Offsets below show time from the operation start (minutes:seconds or hours:minutes:seconds).</p>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
<div class="identity-row">
|
|
|
|
|
{#if deployment}<div class="operation-id"><span>Operation</span><CopyValue value={deployment.id} label="operation ID" /></div>{/if}
|
|
|
|
|
<div class="identity-links"><Button size="sm" variant="ghost" onclick={copyLink}>Copy link</Button>{#if copyStatus}<span role="status">{copyStatus}</span>{/if}</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Freshness updatedAt={view.refreshedAt ? Date.parse(view.refreshedAt) : null} error={view.loadError ? 'Operation updates interrupted.' : ''} intervalLabel="Updates every 5 seconds" onRetry={() => void controller.forceRefresh()} retryDisabled={view.refreshing || !!view.busy} />
|
|
|
|
|
{#if view.loadError}<div class="refresh-error" role="alert"><div><strong>Updates unavailable</strong><p>{view.loadError} {detail ? 'Showing the last successfully loaded record.' : ''}</p></div></div>{/if}
|
|
|
|
|
{#if active}
|
|
|
|
|
<Freshness updatedAt={view.refreshedAt ? Date.parse(view.refreshedAt) : null} error={view.loadError ? 'Operation updates interrupted.' : ''} intervalLabel="Updates every 5 seconds" onRetry={() => void controller.forceRefresh()} retryDisabled={view.refreshing || !!view.busy} />
|
|
|
|
|
{/if}
|
|
|
|
|
{#if view.loadError}<div class="refresh-error" role="alert"><div><strong>Updates unavailable</strong><p>{view.loadError} {detail ? 'Showing the last successfully loaded record.' : ''}</p></div>{#if !active}<Button size="sm" onclick={() => void controller.forceRefresh()} disabled={view.refreshing || !!view.busy}>Retry update</Button>{/if}</div>{/if}
|
|
|
|
|
|
|
|
|
|
{#if deployment && detail && outcome}
|
|
|
|
|
<section class="outcome" class:attention={['failed', 'interrupted', 'warning'].includes(deployment.state)} aria-labelledby="outcome-title">
|
|
|
|
|
<div class="outcome-top"><div class="badges"><StatusBadge state={deployment.state} />{#if deployment.server_deleted_at}<Badge>Deleted identity</Badge>{/if}{#if detail.progress?.recovery?.state === 'held'}<Badge variant="warning">Recovery capacity held</Badge>{/if}</div><span class="record-label">Operation outcome</span></div>
|
|
|
|
|
<div class="badges"><StatusBadge state={deployment.state} />{#if deployment.server_deleted_at}<Badge>Deleted identity</Badge>{/if}{#if detail.progress?.recovery?.state === 'held'}<Badge variant="warning">Recovery capacity held</Badge>{/if}</div>
|
|
|
|
|
<div class="outcome-message" aria-live="polite" aria-atomic="true">
|
|
|
|
|
<h2 id="outcome-title">{outcome.headline}</h2><p>{outcome.description}</p>
|
|
|
|
|
{#if outcome.checksSummary}<p><span aria-hidden="true">✓</span>{' '}{outcome.checksSummary}</p>{/if}
|
|
|
|
@@ -147,7 +149,7 @@
|
|
|
|
|
{#if detail.progress?.recovery}<div class="recovery-note"><strong>{detail.progress.recovery.state === 'held' ? 'Recovery remains in progress' : detail.progress.recovery.state === 'released' ? 'Recovery capacity released' : detail.progress.recovery.state === 'transferred' ? 'Recovery transferred' : 'Recovery state not confirmed'}</strong><p>{detail.progress.recovery.message}</p><span>Recorded <OperationTime start={deployment.started_at} value={detail.progress.recovery.at} missing="Not recorded" /></span>{#if detail.progress.recovery.operationId && detail.progress.recovery.operationId !== deployment.id}<a href={`/provisioning/${encodeURIComponent(detail.progress.recovery.operationId)}`}>View recovery operation →</a>{/if}</div>{/if}
|
|
|
|
|
{#if serverHref && !['done', 'cancelled'].includes(deployment.state)}<div class="diagnostics"><a href={`${serverHref}?tab=logs`}>Open server logs</a><a href={`${serverHref}?tab=diagnostics`}>Diagnose connection</a>{#if detail.progress?.phase === 'verify-map' || detail.progress?.checks.some(check => check.key === 'map' && check.result !== 'pass')}<a href={`${serverHref}?tab=config`}>Review map configuration</a>{/if}</div>{/if}
|
|
|
|
|
<div class="timing">
|
|
|
|
|
<div><span>{outcome.active ? 'Total elapsed' : 'Recorded duration'}</span><strong>{finishedAt || outcome.active ? elapsedTime(startedAt, finishedAt ? Date.parse(finishedAt) : now) : 'Not recorded'}</strong></div>
|
|
|
|
|
<div><span>{outcome.active ? 'Total elapsed' : 'Recorded duration'}</span><strong>{duration}</strong></div>
|
|
|
|
|
{#if outcome.active}<div><span>Time in phase</span><strong>{elapsedTime(timestampISO(detail.progress?.phaseStartedAt), now)}</strong></div>{/if}
|
|
|
|
|
<div><span>Last evaluated</span><OperationTime start={deployment.started_at} value={detail.progress?.observedAt} missing="Not recorded" /></div>
|
|
|
|
|
</div>
|
|
|
|
@@ -155,7 +157,10 @@
|
|
|
|
|
|
|
|
|
|
{#key deployment.id}
|
|
|
|
|
<div class="detail-grid">
|
|
|
|
|
<div class="execution-column"><ProvisioningProgress {detail} hostHistoryHref={contextHref(`/hosts/operations?server=${encodeURIComponent(deployment.server)}`, $page.url)} /><ProvisioningActivity {detail} /></div>
|
|
|
|
|
<div class="execution-column">
|
|
|
|
|
<div class="execution-progress"><p class="timing-note">Offsets below show time from the operation start (minutes:seconds or hours:minutes:seconds).</p><ProvisioningProgress {detail} hostHistoryHref={contextHref(`/hosts/operations?server=${encodeURIComponent(deployment.server)}`, $page.url)} /></div>
|
|
|
|
|
<ProvisioningActivity {detail} />
|
|
|
|
|
</div>
|
|
|
|
|
<aside class="context-column" aria-label="Operation context">
|
|
|
|
|
{#if outcome.active && detail.workerActivity}
|
|
|
|
|
<Card as="section" padding="md" ariaLabel="Controller activity"><div class="context-section">
|
|
|
|
@@ -166,15 +171,9 @@
|
|
|
|
|
</ul>{:else}<p>Waiting for the next provisioning pass.</p>{/if}
|
|
|
|
|
</div></Card>
|
|
|
|
|
{/if}
|
|
|
|
|
<Card as="section" padding="md" ariaLabel="Operation details">
|
|
|
|
|
<div class="context-section"><h2>Operation details</h2><dl class="metadata">
|
|
|
|
|
<div><dt>Submission attempted</dt><dd><OperationTime start={deployment.started_at} value={deployment.submitted_at} missing="Not recorded" /></dd></div>
|
|
|
|
|
{#if deployment.server_deleted_at}<div><dt>Identity deleted</dt><dd><OperationTime start={deployment.started_at} value={deployment.server_deleted_at} /></dd></div>{/if}
|
|
|
|
|
</dl></div>
|
|
|
|
|
</Card>
|
|
|
|
|
{#if detail.sources.length || detail.collection}<Card as="section" padding="md" ariaLabel="Related work"><div class="context-section"><h2>Related work</h2><p>The requesting work may have further checks after this operation finishes.</p><ul class="related">
|
|
|
|
|
{#if detail.collection}<li><a href={`/collections/launches/${encodeURIComponent(detail.collection.id)}`}>{detail.collection.name}</a><span>Collection launch</span></li>{/if}
|
|
|
|
|
{#each detail.sources as source (`${source.kind}:${source.source_id}`)}<li>{#if source.href}<a href={source.href}>{source.label}</a>{:else}<strong>{source.label}</strong><span>Source record no longer available</span>{/if}<code>{source.source_id}</code></li>{/each}
|
|
|
|
|
{#each detail.sources as source (`${source.kind}:${source.source_id}`)}<li>{#if source.href}<a href={source.href}>{source.label}</a>{:else}<strong>{source.label}</strong><span>Source record no longer available</span>{/if}</li>{/each}
|
|
|
|
|
</ul></div></Card>{/if}
|
|
|
|
|
{#if detail.capacity.pools.length}<Card as="section" padding="md" ariaLabel="Capacity"><div class="context-section"><h2>Capacity</h2>
|
|
|
|
|
{#each detail.capacity.pools as pool (pool.kind)}<div class="capacity-pool"><div class="capacity-heading"><strong>{pool.kind === 'startup' ? 'Startup slots' : 'Restart recovery slots'}</strong><span>{pool.used} / {pool.limit} in use</span></div>
|
|
|
|
@@ -192,6 +191,21 @@
|
|
|
|
|
<div class="launch-another"><Button variant="ghost" onclick={() => launchOpen = true}>Launch another server →</Button><p>Creates a separate server with a fresh ID and port using the latest promoted files. It does not repair this instance.</p></div>
|
|
|
|
|
{:else}<p>{['create', 'rebuild'].includes(deployment.action) ? 'No launch recipe was recorded for this operation.' : 'This operation uses the existing instance. No new launch files were selected.'}</p>{/if}
|
|
|
|
|
</div></Card>
|
|
|
|
|
<details class="technical-details">
|
|
|
|
|
<summary>Technical details</summary>
|
|
|
|
|
<div class="technical-content">
|
|
|
|
|
<dl class="metadata">
|
|
|
|
|
<div><dt>Operation ID</dt><dd><CopyValue value={deployment.id} label="operation ID" /></dd></div>
|
|
|
|
|
{#if !active || finishedAt}<div><dt>Finished</dt><dd>{#if finishedAt}<time datetime={finishedAt}>{$timeFormat.dateTime(finishedAt)}</time>{:else}Not recorded{/if}</dd></div>{/if}
|
|
|
|
|
<div><dt>Submission attempted</dt><dd><OperationTime start={deployment.started_at} value={deployment.submitted_at} missing="Not recorded" /></dd></div>
|
|
|
|
|
{#if deployment.server_deleted_at}<div><dt>Identity deleted</dt><dd><OperationTime start={deployment.started_at} value={deployment.server_deleted_at} /></dd></div>{/if}
|
|
|
|
|
{#if detail.agentOperation}<div><dt>Agent command ID</dt><dd><CopyValue value={detail.agentOperation.id} label="agent command ID" /></dd></div>{/if}
|
|
|
|
|
{#if detail.collection}<div><dt>Collection launch ID</dt><dd><CopyValue value={detail.collection.id} label="collection launch ID" /></dd></div>{/if}
|
|
|
|
|
{#each detail.sources as source (`${source.kind}:${source.source_id}`)}<div><dt>{source.label} · Source ID</dt><dd><CopyValue value={source.source_id} label={`${source.label} source ID`} /></dd></div>{/each}
|
|
|
|
|
</dl>
|
|
|
|
|
<div class="copy-link"><Button size="sm" variant="ghost" onclick={copyLink}>Copy link</Button>{#if copyStatus}<span role="status">{copyStatus}</span>{/if}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</details>
|
|
|
|
|
</aside>
|
|
|
|
|
</div>
|
|
|
|
|
{#if detail.recipe}<DeployServerDialog bind:open={launchOpen} sourceDeploymentId={deployment.id} />{/if}
|
|
|
|
@@ -206,12 +220,13 @@
|
|
|
|
|
h2, h3, p { margin: 0; }a { color: var(--accent-primary-hover); text-underline-offset: 3px; }
|
|
|
|
|
a:focus-visible, summary:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 3px; border-radius: 2px; }
|
|
|
|
|
p { line-height: 1.65; overflow-wrap: anywhere; }
|
|
|
|
|
.operation-header { display: grid; gap: var(--space-3); }
|
|
|
|
|
.operation-summary { color: var(--text-secondary); font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
|
|
|
|
|
.operation-summary .timing-note { margin-top: var(--space-2); font-size: var(--text-xs); }
|
|
|
|
|
.identity-row, .operation-id, .identity-links, .badges, .outcome-top, .diagnostics, .recovery-actions { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
|
|
|
|
|
.identity-row { justify-content: space-between; margin-top: calc(-1 * var(--space-3)); gap: var(--space-2) var(--space-5); }.operation-id { gap: var(--space-2); min-width: 0; }.operation-id > span, .identity-links { font-size: var(--text-xs); color: var(--text-tertiary); }.identity-links { gap: var(--space-4); }
|
|
|
|
|
.execution-progress { display: grid; gap: var(--space-4); }
|
|
|
|
|
.timing-note { font-size: var(--text-xs); color: var(--text-tertiary); }
|
|
|
|
|
.badges, .diagnostics, .recovery-actions { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
|
|
|
|
|
.outcome { display: grid; gap: var(--space-5); padding: var(--space-6); border: 1px solid var(--border-strong); border-radius: var(--radius-lg); background: var(--surface); }
|
|
|
|
|
.outcome.attention { border-color: color-mix(in srgb, var(--state-warning) 35%, var(--border-strong)); }.outcome-top { justify-content: space-between; }.record-label { color: var(--text-tertiary); font-size: var(--text-xs); }.outcome h2 { font-size: var(--text-xl); line-height: 1.35; letter-spacing: var(--tracking-tight); overflow-wrap: anywhere; }
|
|
|
|
|
.outcome.attention { border-color: color-mix(in srgb, var(--state-warning) 35%, var(--border-strong)); }.outcome h2 { font-size: var(--text-xl); line-height: 1.35; letter-spacing: var(--tracking-tight); overflow-wrap: anywhere; }
|
|
|
|
|
.outcome-message { display: grid; gap: var(--space-2); }.outcome-message p { color: var(--text-secondary); font-size: var(--text-base); max-width: 85ch; }
|
|
|
|
|
.failure, .action-error, .refresh-error { border: 1px solid color-mix(in srgb, var(--state-danger) 35%, var(--border-subtle)); border-radius: var(--radius-md); padding: var(--space-4); color: var(--state-danger); font-size: var(--text-sm); }.failure p, .action-error p { margin-top: var(--space-2); color: var(--text-primary); }.refresh-error { display: flex; justify-content: space-between; align-items: center; gap: var(--space-4); flex-wrap: wrap; }.refresh-error p { color: var(--text-secondary); margin-top: var(--space-1); }
|
|
|
|
|
.failure-excerpt { display: grid; gap: var(--space-1); font-size: var(--text-sm); color: var(--text-secondary); }.failure-excerpt span { font-size: var(--text-xs); color: var(--text-tertiary); }
|
|
|
|
@@ -221,10 +236,14 @@
|
|
|
|
|
.timing { border-top: 1px solid var(--border-subtle); padding-top: var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-4) var(--space-8); }.timing > div { display: grid; gap: var(--space-2); }.timing span { font-size: var(--text-xs); color: var(--text-tertiary); }.timing strong { font-size: var(--text-md); font-variant-numeric: tabular-nums; font-weight: var(--weight-medium); }.timing :global(time) { font-size: var(--text-sm); color: var(--text-secondary); }
|
|
|
|
|
.detail-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(280px, 340px); gap: var(--space-8); align-items: start; }.execution-column { display: grid; gap: var(--space-8); min-width: 0; }.context-column { display: grid; gap: var(--space-5); min-width: 0; }.context-section { display: grid; gap: var(--space-4); min-width: 0; }.context-section h2 { font-size: var(--text-md); }.context-section p { font-size: var(--text-sm); color: var(--text-secondary); }.context-section p.small { font-size: var(--text-xs); }
|
|
|
|
|
.metadata { display: grid; gap: var(--space-4); margin: 0; }.metadata dt { color: var(--text-tertiary); font-size: var(--text-xs); }.metadata dd { margin: var(--space-1) 0 0; font-size: var(--text-sm); line-height: 1.6; overflow-wrap: anywhere; }
|
|
|
|
|
.related, .capacity-pool ul { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-4); }.related li { display: grid; gap: var(--space-1); font-size: var(--text-sm); overflow-wrap: anywhere; }.related span, .related code { color: var(--text-tertiary); font-size: var(--text-xs); }.related strong { font-weight: var(--weight-medium); }code { font-family: var(--font-mono); }
|
|
|
|
|
.technical-details { border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); background: var(--surface); min-width: 0; }
|
|
|
|
|
.technical-details > summary { padding: var(--space-5); cursor: pointer; font-size: var(--text-sm); font-weight: var(--weight-semibold); }
|
|
|
|
|
.technical-content { display: grid; gap: var(--space-4); padding: 0 var(--space-5) var(--space-5); }
|
|
|
|
|
.copy-link { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; font-size: var(--text-xs); color: var(--text-secondary); }
|
|
|
|
|
.related, .capacity-pool ul { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-4); }.related li { display: grid; gap: var(--space-1); font-size: var(--text-sm); overflow-wrap: anywhere; }.related span { color: var(--text-tertiary); font-size: var(--text-xs); }.related strong { font-weight: var(--weight-medium); }
|
|
|
|
|
.capacity-pool { display: grid; gap: var(--space-3); font-size: var(--text-xs); }.capacity-pool + .capacity-pool { border-top: 1px solid var(--border-subtle); padding-top: var(--space-4); }.capacity-heading { display: flex; justify-content: space-between; gap: var(--space-2); flex-wrap: wrap; }.capacity-heading strong { font-weight: var(--weight-medium); }.capacity-heading span { color: var(--text-secondary); }.capacity-pool summary { cursor: pointer; color: var(--text-secondary); }.capacity-pool ul { gap: var(--space-2); margin-top: var(--space-3); line-height: 1.6; }
|
|
|
|
|
.launch-another { display: grid; gap: var(--space-2); justify-items: start; border-top: 1px solid var(--border-subtle); padding-top: var(--space-4); }.launch-another p { font-size: var(--text-xs); }
|
|
|
|
|
.loading { min-height: 240px; padding: var(--space-6); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); color: var(--text-secondary); }
|
|
|
|
|
@media (max-width: 1000px) { .detail-grid { grid-template-columns: minmax(0, 1fr); }.context-column { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
|
|
|
@media (max-width: 600px) { .page { gap: var(--space-5); }.outcome { padding: var(--space-4); gap: var(--space-4); }.outcome h2 { font-size: var(--text-lg); }.outcome-message p { font-size: var(--text-sm); }.context-column { grid-template-columns: minmax(0, 1fr); }.identity-row, .identity-links { align-items: flex-start; }.operation-id { display: block; }.operation-id > span { display: block; margin-bottom: var(--space-1); }.outcome-top { align-items: flex-start; }.record-label { display: none; }.timing { gap: var(--space-4); }.timing > div:last-child { flex-basis: 100%; }.detail-grid { gap: var(--space-6); } }
|
|
|
|
|
@media (max-width: 600px) { .page { gap: var(--space-5); }.outcome { padding: var(--space-4); gap: var(--space-4); }.outcome h2 { font-size: var(--text-lg); }.outcome-message p { font-size: var(--text-sm); }.context-column { grid-template-columns: minmax(0, 1fr); }.technical-details > summary { padding: var(--space-4); }.technical-content { padding: 0 var(--space-4) var(--space-4); }.timing { gap: var(--space-4); }.timing > div:last-child { flex-basis: 100%; }.detail-grid { gap: var(--space-6); } }
|
|
|
|
|
</style>
|
|
|
|
|