chore: sprint A — мелкая полировка

#15 a11y: <html lang> синхронизируется с settings.language через
    ThemeProvider — screen-readers больше не читают EN-текст с
    русским акцентом и наоборот.
#14 dev:simulateMatchEnd channel вынесен в IPC enum
    (IPC.devSimulateMatchEnd) — main/preload не разойдутся в hardcoded
    строках.
#34 ChallengeEditor: multiplier клампится к [0.5, 1000] (max="1000",
    Math.min(1000, ...)). Совпадает с validate.ts — раньше save с 9999
    молча отклонялся IPC, теперь UI не даёт ввести.
#28 package.json: добавлен `test:coverage` script.
This commit is contained in:
AnRil
2026-05-22 01:13:01 +07:00
parent 9378cabfe5
commit a41dce511b
6 changed files with 25 additions and 3 deletions

View File

@@ -14,6 +14,7 @@
"typecheck": "npm run typecheck:node && npm run typecheck:web", "typecheck": "npm run typecheck:node && npm run typecheck:web",
"test": "vitest", "test": "vitest",
"test:run": "vitest run", "test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"format": "prettier --write \"src/**/*.{ts,tsx,css}\" \"*.{json,md}\" \".github/**/*.yml\"", "format": "prettier --write \"src/**/*.{ts,tsx,css}\" \"*.{json,md}\" \".github/**/*.yml\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx,css}\" \"*.{json,md}\"", "format:check": "prettier --check \"src/**/*.{ts,tsx,css}\" \"*.{json,md}\"",
"lint": "eslint src --ext .ts,.tsx --max-warnings 0", "lint": "eslint src --ext .ts,.tsx --max-warnings 0",

View File

@@ -275,7 +275,7 @@ export function registerIpc(): void {
// otherwise fabricate arbitrary match-end events at will. // otherwise fabricate arbitrary match-end events at will.
if (!app.isPackaged) { if (!app.isPackaged) {
ipcMain.handle( ipcMain.handle(
'dev:simulateMatchEnd', IPC.devSimulateMatchEnd,
(_e, id: GameId, stats: Record<string, number>) => { (_e, id: GameId, stats: Record<string, number>) => {
simulateMatchEnd(id, stats) simulateMatchEnd(id, stats)
} }

View File

@@ -96,7 +96,7 @@ const api = {
id: GameId, id: GameId,
stats: Record<string, number> stats: Record<string, number>
): Promise<void> => ): Promise<void> =>
ipcRenderer.invoke('dev:simulateMatchEnd', id, stats) ipcRenderer.invoke(IPC.devSimulateMatchEnd, id, stats)
} }
: {}), : {}),

View File

@@ -264,11 +264,18 @@ function ChallengeEditor({
type="number" type="number"
step="0.5" step="0.5"
min="0.5" min="0.5"
max="1000"
value={draft.multiplier} value={draft.multiplier}
onChange={(e) => onChange={(e) =>
setDraft({ setDraft({
...draft, ...draft,
multiplier: Math.max(0.5, Number(e.target.value) || 1) // Клампим к диапазону [0.5, 1000] — совпадает с validate.ts
// (multiplier ∈ [0, 1000]). Без max=1000 пользователь мог
// ввести 9999 и save молча отклонялся IPC-валидатором.
multiplier: Math.max(
0.5,
Math.min(1000, Number(e.target.value) || 1)
)
}) })
} }
className="ios-input font-mono-num" className="ios-input font-mono-num"

View File

@@ -25,5 +25,15 @@ export function ThemeProvider({
else document.documentElement.classList.remove('dark') else document.documentElement.classList.remove('dark')
}, [settings?.theme, osTheme]) }, [settings?.theme, osTheme])
// Синхронизируем <html lang> с языком приложения. Без этого screen-readers
// продолжают читать английский текст как кириллицу (или ломаются) при
// переключении на EN, и наоборот — это a11y-баг.
useEffect(() => {
const lang = settings?.language ?? 'ru'
if (document.documentElement.lang !== lang) {
document.documentElement.lang = lang
}
}, [settings?.language])
return <>{children}</> return <>{children}</>
} }

View File

@@ -38,6 +38,10 @@ export const IPC = {
markChallengeDone: 'challenge:markDone', markChallengeDone: 'challenge:markDone',
closeMatchSummary: 'matchSummary:close', closeMatchSummary: 'matchSummary:close',
// Dev-only IPC (handler ungated в prod, см. ipc.ts). Держим в enum чтобы
// main/preload/renderer не разошлись в hardcoded-строках.
devSimulateMatchEnd: 'dev:simulateMatchEnd',
// Auto-updater // Auto-updater
updaterStatus: 'updater:status', updaterStatus: 'updater:status',
updaterCheck: 'updater:check', updaterCheck: 'updater:check',