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:
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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}</>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user