# План этапа 7 — полировка Дата: 2026-08-06. Статус: завершён (431/431, dist 296 КБ). Предшественник: этап 6 завершён, main = 2b249a5 (413/413, dist 280 КБ). ## Ключевые решения (не пере-решать) 1. .htaccess — apps/web/public/.htaccess (Astro копирует public/ в dist как есть); содержимое зафиксировано в INTERFACES.md; HTTPS-редирект сознательно ОТСУТСТВУЕТ (только панель хостинга, gotcha-https-redirect-proxy). 2. Клавиатурный ввод — НЕ новый код в packages/board: чистый редьюсер курсора в apps/web/src/lib/keyboard.ts, подтверждение хода идёт через существующий reduceInput (tap по курсору → фантом → повтор → commit). Семантика подтверждения едина с тач-вводом (I.6, одна механика). 3. ARIA-live — role="status" polite на существующих статусных строках, без live-region-спама: меняется только текстовое содержимое. 4. Бюджет 300 КБ — по сырым байтам dist (с запасом к gzip); тест считает html + локальные ассеты, на которые страница ссылается. 5. Отметка «урок пройден» — ручная кнопка + markLessonDone в storage.ts; авто- done для уроков с задачами (этап 6) сохраняется, кнопка — дополнение, не замена. 6. Зона — только apps/web; packages и корневые конфиги не трогаем. ## Объём - public/.htaccess + wiring-проверка директив в dist. - keyboard.ts (reduceCursor, кламп, тесты) + подписки в play-page.ts и task-widget.ts (курсор, стрелки/Enter/Space/Escape, aria-labels). - prefers-reduced-motion блок, :focus-visible, контроль контраста (--muted при недоборе), role="status" на статусах. - Кнопка «Отметить пройденным» на странице урока + markLessonDone + тест. - Wiring-тест бюджета веса уроков ≤300 КБ. - Строки — strings.ts. ## Приёмка этапа (и критерии v1, что можно проверить автоматически) - npm run gates зелёные, 0 warnings; astro build PASS. - dist/.htaccess с требуемыми директивами; без RewriteCond %{HTTPS}. - Каждая страница урока ≤ 300 КБ по тесту. - Критерии v1: №1 (тесты) и №4 (чистый клон собирается) — автоматически; №2 (партия vs ур.5), №3 (тайминги бота), №5 (мобильный Safari) — ручные, владельцу. ## Риски - Клавиатурный курсор не должен ломать pointer-ввод: общий InputState, подписки независимы. - suggestDeadGroups грубовата (PROJECT_STATE) — в этап 7 НЕ входит (отдельная задача через ADR при жалобах на подсчёт).