Add persistent connection status live region

This commit is contained in:
Jage9
2026-02-22 19:54:55 -05:00
parent 05e92fcb64
commit f7171487c2
4 changed files with 27 additions and 1 deletions

View File

@@ -8,6 +8,7 @@
<body> <body>
<main class="app"> <main class="app">
<h1>Chat Grid</h1> <h1>Chat Grid</h1>
<div id="connectionStatus" role="status" aria-live="polite" aria-atomic="true"></div>
<div id="nicknameContainer" class="nickname-row"> <div id="nicknameContainer" class="nickname-row">
<label for="preconnectNickname">Nickname</label> <label for="preconnectNickname">Nickname</label>
<input id="preconnectNickname" type="text" maxlength="32" autocomplete="nickname" /> <input id="preconnectNickname" type="text" maxlength="32" autocomplete="nickname" />

View File

@@ -1,5 +1,5 @@
// Maintainer-controlled web client version. // Maintainer-controlled web client version.
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2) // Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
window.CHGRID_WEB_VERSION = "2026.02.22 R179"; window.CHGRID_WEB_VERSION = "2026.02.22 R180";
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid. // Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
window.CHGRID_TIME_ZONE = "America/Detroit"; window.CHGRID_TIME_ZONE = "America/Detroit";

View File

@@ -89,6 +89,7 @@ declare global {
} }
type Dom = { type Dom = {
connectionStatus: HTMLElement;
appVersion: HTMLElement; appVersion: HTMLElement;
updatesSection: HTMLElement; updatesSection: HTMLElement;
updatesToggle: HTMLButtonElement; updatesToggle: HTMLButtonElement;
@@ -111,6 +112,7 @@ type Dom = {
}; };
const dom: Dom = { const dom: Dom = {
connectionStatus: requiredById('connectionStatus'),
appVersion: requiredById('appVersion'), appVersion: requiredById('appVersion'),
updatesSection: requiredById('updatesSection'), updatesSection: requiredById('updatesSection'),
updatesToggle: requiredById('updatesToggle'), updatesToggle: requiredById('updatesToggle'),
@@ -419,6 +421,11 @@ function updateStatus(message: string): void {
}, 4000); }, 4000);
} }
/** Updates persistent connection/update status shown under the page heading. */
function setConnectionStatus(message: string): void {
dom.connectionStatus.textContent = String(message).trim();
}
/** Sanitizes user nicknames to printable/safe characters and enforces max length. */ /** Sanitizes user nicknames to printable/safe characters and enforces max length. */
function sanitizeName(value: string): string { function sanitizeName(value: string): string {
return value.replace(/[\u0000-\u001F\u007F<>]/g, '').trim().slice(0, NICKNAME_MAX_LENGTH); return value.replace(/[\u0000-\u001F\u007F<>]/g, '').trim().slice(0, NICKNAME_MAX_LENGTH);
@@ -577,11 +584,13 @@ function handleSignalingStatus(message: string): void {
return; return;
} }
if (message === 'Disconnected.' && state.running && !reconnectInFlight) { if (message === 'Disconnected.' && state.running && !reconnectInFlight) {
setConnectionStatus('Disconnected from server. Reconnecting...');
pushChatMessage('Disconnected from server. Reconnecting...'); pushChatMessage('Disconnected from server. Reconnecting...');
void reconnectAfterSocketClose(); void reconnectAfterSocketClose();
return; return;
} }
if (message === 'Disconnected.') { if (message === 'Disconnected.') {
setConnectionStatus('Disconnected from server.');
pushChatMessage('Disconnected from server.'); pushChatMessage('Disconnected from server.');
return; return;
} }
@@ -1240,6 +1249,11 @@ function getConnectionFlowDeps(): ConnectFlowDeps {
dom, dom,
sanitizeName, sanitizeName,
updateStatus: (message) => { updateStatus: (message) => {
if (message === 'Disconnected.') {
setConnectionStatus('Disconnected.');
} else if (message.startsWith('Connect failed.')) {
setConnectionStatus(message);
}
if (reconnectInFlight && message === 'Disconnected.') { if (reconnectInFlight && message === 'Disconnected.') {
return; return;
} }
@@ -1271,6 +1285,7 @@ function getConnectionFlowDeps(): ConnectFlowDeps {
/** Performs end-to-end connect flow: validation, media setup, then signaling connection. */ /** Performs end-to-end connect flow: validation, media setup, then signaling connection. */
async function connect(): Promise<void> { async function connect(): Promise<void> {
setConnectionStatus('Connecting...');
await runConnectFlow(getConnectionFlowDeps()); await runConnectFlow(getConnectionFlowDeps());
} }
@@ -1278,6 +1293,7 @@ async function connect(): Promise<void> {
function disconnect(): void { function disconnect(): void {
stopHeartbeat(); stopHeartbeat();
runDisconnectFlow(getConnectionFlowDeps()); runDisconnectFlow(getConnectionFlowDeps());
setConnectionStatus('Disconnected.');
pendingEscapeDisconnect = false; pendingEscapeDisconnect = false;
restoreLoopbackAfterMicGainEdit(); restoreLoopbackAfterMicGainEdit();
subscriptionRefreshPending = false; subscriptionRefreshPending = false;
@@ -1377,10 +1393,12 @@ async function onSignalingMessage(message: IncomingMessage): Promise<void> {
await onAppMessage(message); await onAppMessage(message);
applyConfiguredPeerListenGains(); applyConfiguredPeerListenGains();
if (restartAnnouncement) { if (restartAnnouncement) {
setConnectionStatus(restartAnnouncement);
pushChatMessage(restartAnnouncement); pushChatMessage(restartAnnouncement);
audio.sfxUiConfirm(); audio.sfxUiConfirm();
} }
if (connectedAnnouncement) { if (connectedAnnouncement) {
setConnectionStatus(connectedAnnouncement);
pushChatMessage(connectedAnnouncement); pushChatMessage(connectedAnnouncement);
} }
} }
@@ -2295,3 +2313,4 @@ updateStatus(
? 'Client updated, please reconnect.' ? 'Client updated, please reconnect.'
: 'Welcome to the Chat Grid. Press the Settings button to configure your audio, then Connect to join the grid.', : 'Welcome to the Chat Grid. Press the Settings button to configure your audio, then Connect to join the grid.',
); );
setConnectionStatus(isVersionReloadedSession() ? 'Client updated, please reconnect.' : 'Not connected.');

View File

@@ -18,6 +18,12 @@ body {
text-align: center; text-align: center;
} }
#connectionStatus {
color: #93c5fd;
min-height: 1.25rem;
margin: 0.25rem 0 0.5rem;
}
#appVersion { #appVersion {
display: block; display: block;
color: #94a3b8; color: #94a3b8;