SPB Git

Repository Public

connection-indicator.tsx 2.4 KB 40 lines tsx

1'use client';2import { useConn } from '@/stores/conn';3import { IDLE_CONN, useTerminals } from '@/stores/terminals';4import { Tooltip } from '@/components/ui/tooltip';5import { Wifi, WifiOff } from 'lucide-react';6import { Dot, type Tone } from '@/components/ui/badge';7import { cn } from '@/lib/cn';89/** Live connection state: events channel + the active terminal stream (with measured latency). */10export function useConnectionSummary(): { tone: Tone; label: string; detail: string; pulse: boolean } {11  const { events, online } = useConn();12  const activeId = useTerminals((s) => s.activeId);13  const term = useTerminals((s) => (activeId ? s.conns[activeId] : undefined)) ?? IDLE_CONN;14  if (!online) return { tone: 'error', label: 'Offline', detail: 'No network. Remote sessions keep running on the server.', pulse: false };15  if (term.status === 'reconnecting' || term.status === 'connecting') return { tone: 'warning', label: 'Reconnecting', detail: 'Terminal stream is reconnecting.', pulse: true };16  if (events !== 'open') return { tone: 'warning', label: events === 'connecting' ? 'Connecting' : 'Reconnecting', detail: 'Live updates channel is not connected.', pulse: true };17  if (term.status === 'open')18    return { tone: 'success', label: term.latencyMs == null ? 'Connected' : `${term.latencyMs} ms`, detail: term.latencyMs == null ? 'Terminal connected; latency not measured yet.' : `Terminal round-trip ${term.latencyMs} ms (measured by ping/pong).`, pulse: false };19  return { tone: 'success', label: 'Connected', detail: 'Live updates connected. No terminal stream is active.', pulse: false };20}2122export function ConnectionIndicator({ compact, className }: { compact?: boolean; className?: string }) {23  const s = useConnectionSummary();24  return (25    <Tooltip content={s.detail}>26      <span role="status" aria-label={`Connection: ${s.label}`} data-testid="conn-indicator" data-tone={s.tone} className={cn('tnum inline-flex items-center gap-1.5 text-[11px] text-text-2', className)}>27        {compact ? (28          // an icon, not a dot: on phones it sits next to the session-state dot29          s.tone === 'error' ? <WifiOff className="size-4 text-error" aria-hidden /> : <Wifi className={cn('size-4', s.tone === 'success' ? 'text-success' : 'text-warning', s.pulse && 'anim-pulse')} aria-hidden />30        ) : (31          <>32            <Dot tone={s.tone} pulse={s.pulse} />33            {s.label}34          </>35        )}36      </span>37    </Tooltip>38  );39}40
Files main
Menu

Theme