Clarify server actions and group maintenance failures

This commit is contained in:
2026-10-07 19:21:53 -04:00
parent bcecc42dd1
commit c39563daef
15 changed files with 559 additions and 111 deletions
+13 -8
View File
@@ -6,8 +6,10 @@
import { pollVisible } from '$ui/poll.js';
import { timeFormat } from '$ui/stores/time-zone.js';
import Button from './primitives/Button.svelte';
import Freshness from './Freshness.svelte';
let { server, history }: { server?: string; history?: ContextualChatEntry[] } = $props();
let { server, history, hostOffline = false, hostName }: { server?: string; history?: ContextualChatEntry[]; hostOffline?: boolean; hostName?: string } = $props();
let updatedAt = $state<number | null>(null);
let liveEntries = $state<ContextualChatEntry[]>([]);
const entries = $derived(history ?? liveEntries);
let timeline = $derived(groupChatTimeline(entries));
@@ -24,7 +26,7 @@
let controller: AbortController | null = null;
async function refresh(manual = false) {
if (!server) return;
if (!server || hostOffline) return;
if (loading) {
if (manual) manualRefreshing = true;
return;
@@ -37,11 +39,11 @@
const response = await fetch(`/api/servers/${encodeURIComponent(server)}/chat`, { signal: request.signal });
const data = await response.json().catch(() => null);
if (!response.ok || !data?.ok) throw new Error('Could not refresh chat. Try again shortly.');
if (request.signal.aborted) return;
if (request.signal.aborted || hostOffline) return;
if (paused && !manual) return;
liveEntries = data.entries ?? [];
missing = !!data.missing;
liveLoaded = true;
liveLoaded = true; updatedAt = Date.now();
error = '';
await tick();
if (follow && body) body.scrollTop = body.scrollHeight;
@@ -50,6 +52,8 @@
} finally { loading = false; manualRefreshing = false; }
}
$effect(() => { if (hostOffline) controller?.abort(); });
function onScroll() {
if (body) follow = body.scrollHeight - body.scrollTop - body.clientHeight < 40;
}
@@ -93,17 +97,17 @@
<section aria-label="Player chat">
<div class="chat-head">
<div class="chat-title"><h2>Chat</h2><span class="update-cadence">{server ? paused ? 'Paused' : 'Live · 5s' : 'Match history'}</span></div>
<div class="chat-title"><h2>Chat</h2>{#if !server}<span class="update-cadence">Match history</span>{:else if hostOffline}<span class="update-cadence">Host offline</span>{:else if paused}<span class="update-cadence">Paused</span>{:else if !error}<Freshness {updatedAt} intervalLabel="every 5 s" />{/if}</div>
{#if server}
<div class="actions">
{#if hasMessages}<Button size="sm" variant="ghost" onclick={() => paused = !paused}>{paused ? 'Resume' : 'Pause'}</Button>{/if}
{#if !follow}<Button size="sm" variant="ghost" onclick={latest}>Latest messages</Button>{/if}
<Button size="sm" onclick={() => refresh(true)} loading={manualRefreshing} ariaLabel={manualRefreshing ? 'Refreshing chat' : 'Refresh chat'}>Refresh</Button>
</div>
{/if}
</div>
{#if error}<p class="ui-error" role="alert">{error} {loaded ? 'Showing the last loaded messages.' : ''}</p>{/if}
{#if !loaded && !error}
{#if hostOffline}<p class="outage">Chat resumes when {hostName ?? 'the host agent'} reconnects.{loaded ? ' Showing the last loaded messages.' : ''}</p>
{:else if error}<p class="ui-error" role="alert">Chat updates paused. {loaded ? 'Showing the last loaded messages.' : 'Could not load messages.'} <Button size="sm" onclick={() => refresh(true)} loading={manualRefreshing}>Retry</Button></p>{/if}
{#if !loaded && !error && !hostOffline}
<p class="empty" role="status">Loading chat…</p>
{:else if loaded && !hasMessages}
<p class="empty">{history !== undefined ? 'No chat messages were saved for this match.' : missing ? 'Waiting for the game log. Player messages will appear here.' : 'No player messages in this match yet.'}</p>
@@ -152,6 +156,7 @@
.chat-title { gap: var(--space-2) var(--space-3); }
h2 { margin: 0; font-size: var(--text-lg); }
.update-cadence { color: var(--text-secondary); font-size: var(--text-xs); }
.outage { color: var(--state-warning); font-size: var(--text-sm); line-height: var(--leading-body); }
.empty { color: var(--text-secondary); font-size: var(--text-sm); padding: 8px 0; margin-bottom: 0; }
.messages { max-height: 360px; overflow: auto; margin-top: var(--space-4); }
.messages:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent-primary); border-radius: var(--radius-sm); }