Frontend: structured error handling, state fixes, accessibility, i18n

- Enhance fetchWithAuth with auto-401, retry w/ exponential backoff, timeout
- Remove ~40 manual 401 checks across 10 feature files
- Fix state: brightness cache setter, manual edit flag resets, static import
- Add ARIA: role=dialog/tablist, aria-modal, aria-labelledby, aria-selected
- Add focus trapping in Modal base class, aria-expanded on hint toggles
- Fix WCAG AA color contrast with --primary-text-color variable
- Add i18n pluralization (CLDR rules for en/ru), getCurrentLocale export
- Replace hardcoded strings in dashboard.js and profiles.js
- Add data-i18n-aria-label support, 20 new keys in en.json and ru.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 01:18:29 +03:00
parent 2b90fafb9c
commit 3ae20761a1
41 changed files with 355 additions and 248 deletions

View File

@@ -6,7 +6,7 @@ import {
_discoveryScanRunning, set_discoveryScanRunning,
_discoveryCache, set_discoveryCache,
} from '../core/state.js';
import { API_BASE, getHeaders, isSerialDevice, escapeHtml, handle401Error } from '../core/api.js';
import { API_BASE, fetchWithAuth, isSerialDevice, escapeHtml } from '../core/api.js';
import { t } from '../core/i18n.js';
import { showToast } from '../core/ui.js';
import { Modal } from '../core/modal.js';
@@ -214,11 +214,7 @@ export async function scanForDevices(forceType) {
if (scanBtn) scanBtn.disabled = true;
try {
const response = await fetch(`${API_BASE}/devices/discover?timeout=3&device_type=${encodeURIComponent(scanType)}`, {
headers: getHeaders()
});
if (response.status === 401) { handle401Error(); return; }
const response = await fetchWithAuth(`/devices/discover?timeout=3&device_type=${encodeURIComponent(scanType)}`);
loading.style.display = 'none';
if (scanBtn) scanBtn.disabled = false;
@@ -240,6 +236,7 @@ export async function scanForDevices(forceType) {
_renderDiscoveryList();
}
} catch (err) {
if (err.isAuth) return;
loading.style.display = 'none';
if (scanBtn) scanBtn.disabled = false;
if (!isSerialDevice(scanType)) {
@@ -298,17 +295,11 @@ export async function handleAddDevice(event) {
body.capture_template_id = lastTemplateId;
}
const response = await fetch(`${API_BASE}/devices`, {
const response = await fetchWithAuth('/devices', {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify(body)
});
if (response.status === 401) {
handle401Error();
return;
}
if (response.ok) {
const result = await response.json();
console.log('Device added successfully:', result);
@@ -330,6 +321,7 @@ export async function handleAddDevice(event) {
error.style.display = 'block';
}
} catch (err) {
if (err.isAuth) return;
console.error('Failed to add device:', err);
showToast('Failed to add device', 'error');
}