Node.js/Express backend + vanilla JS frontend. Features: real-time collaborative whiteboard (SSE), multi-page support, LaTeX formulas, shapes/connectors, coordinate systems, number lines, compass, zoom/pan, Catmull-Rom pencil smoothing, ruler/protractor with rotation & resize controls, minimap navigation overlay, auto-measurements, multi-page thumbnails sidebar, PNG export, page templates. Student/teacher workflows: classes, assignments, library, dashboard. Mobile responsive. SQLite (better-sqlite3). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
35 KiB
LearnSpace — Полный UX-аудит фронтенда
Дата проверки: 2026-03-16 Проверено файлов: 40 Методология: ручной просмотр каждого HTML-файла, оценка по 10 категориям
Сводная таблица критических проблем
| # | Файл | Проблема | Серьёзность |
|---|---|---|---|
| 1 | board.html | Кнопка «Перейти» ведёт на /dashboard, а не запускает тест |
HIGH |
| 2 | login.html | После регистрации всегда редирект на /dashboard — учителя попадают не туда |
HIGH |
| 3 | homework.html | catch {} — пустой обработчик ошибок, студент не знает о сбое |
HIGH |
| 4 | test-result.html | Обе кнопки «К тестам» и «В кабинет» ведут на /dashboard — дублирование без смысла |
HIGH |
| 5 | live-quiz.html | Кнопка «Завершить сессию» без подтверждения — деструктивное действие | HIGH |
| 6 | admin.html | Смена роли пользователя без подтверждения (inline select) | HIGH |
| 7 | board.html | Ссылка href="/red-book.html" — с расширением .html, все остальные роуты без расширения |
MEDIUM |
| 8 | Все страницы | Analytics, Gradebook, Question Bank, Live Quiz, Lesson Editor — нет в сайдбаре | MEDIUM |
| 9 | profile.html | overflow: hidden на body — может сломаться при виртуальной клавиатуре на мобиле |
MEDIUM |
| 10 | profile.html | Форма смены пароля без поля «текущий пароль» | MEDIUM |
Детальный разбор по файлам
login.html
[HIGH] Flow: редирект после регистрации всегда на /dashboard
Учителя и администраторы после создания аккаунта попадают на страницу студента. Им нужны /classes или /admin.
Фикс: после успешной регистрации получать роль из ответа API и редиректить по роли: student → /dashboard, teacher → /classes, admin → /admin.
[MEDIUM] Flow: нет выбора роли при регистрации
Непонятно, как учитель или администратор создаёт аккаунт. Нет поля «роль» в форме регистрации.
Фикс: добавить <select> с вариантами «Студент» / «Учитель». Роль «Администратор» создаётся только через admin-панель.
[MEDIUM] Accessibility: нет ссылки «Забыли пароль?»
Стандартный элемент auth-страниц полностью отсутствует. Фикс: добавить ссылку под полем пароля на форме входа.
[LOW] Consistency: showBtnSuccess вставляет строку '✓ Сохранено' через конкатенацию текста
Использование символа ✓ в коде (эмодзи-подобный символ) противоречит правилу проекта «только SVG-иконки».
Фикс: заменить '✓ ' на inline SVG с классом .ic.
dashboard.html
[MEDIUM] Loading: у виджетов есть скелетоны, но нет fallback при сетевой ошибке
Если API возвращает 500 — скелетон не убирается и данные не появляются. Пользователь не понимает, что произошло.
Фикс: в каждом catch блоке заменять скелетон на rich-empty с текстом ошибки и кнопкой «Повторить».
[MEDIUM] Navigation: кнопки быстрых действий (adm-actions) видны для teacher/admin, но не имеют состояния загрузки
После нажатия на «Создать тест» или «Добавить класс» кнопка не блокируется — возможно двойное нажатие.
Фикс: btn.disabled = true сразу при клике.
[LOW] Feedback: таймер дедлайна предупреждает только за 2 минуты (secondsLeft <= 120)
Для длинных тестов (1 час+) единственное предупреждение придёт слишком поздно, если пользователь отвлёкся. Фикс: добавить промежуточное предупреждение при 25% оставшегося времени и за 5 минут.
board.html
[HIGH] Flow: кнопка «Перейти» на карточке задания ведёт на /dashboard, а не на тест
<a href="/dashboard" class="btn-action">Перейти</a>
Студент не может начать задание прямо с борда — он попадает на главную страницу.
Фикс: ссылка должна вести на /test-run?session=...&assignment=... (с нужными параметрами задания).
[MEDIUM] Consistency: ссылка на Red Book в сайдбаре содержит .html-расширение
<a href="/red-book.html" ...>
Все остальные ссылки: /dashboard, /board, /classes — без расширения.
Фикс: добавить Express-роут /red-book → отдавать red-book.html, изменить ссылку на /red-book. Это же исправление нужно во всех файлах с сайдбаром.
[MEDIUM] Navigation: студент без класса видит пустой бord без способа вступить по инвайт-коду
Пустое состояние есть, но нет поля ввода инвайт-кода. Фикс: добавить в empty-state форму «Введи код класса» с полем и кнопкой.
[MEDIUM] Feedback: бейдж «LIVE» рендерится всегда, независимо от статуса
<span class="live-badge">LIVE</span>
JS должен скрывать/показывать его по реальному статусу сессии.
Фикс: liveBadge.style.display = session.is_live ? '' : 'none'.
[LOW] Data integrity: реакции сохраняются только в localStorage
Данные ls_reactions пропадут на другом браузере/устройстве и не видны другим пользователям.
Фикс: сохранять реакцию через POST /api/posts/:id/react, localStorage использовать только как оптимистичный кэш.
classes.html
[MEDIUM] Forms: кнопка «Создать класс» видна до выполнения JS role-check
В HTML btn-new-cl отрисовывается сразу. Студент, быстро открыв страницу, видит её до скрытия.
Фикс: добавить style="display:none" в HTML, показывать через JS только для teacher/admin.
[MEDIUM] Navigation: ссылка на Gradebook скрыта в маленькой иконке в заголовке сайдбара
Новый пользователь никогда не найдёт журнал оценок. Фикс: вынести «Журнал» как отдельный пункт сайдбара (или добавить явную кнопку в шапку детали класса).
[MEDIUM] Confirmation: кнопка btn-delete-class — неясно, есть ли подтверждение
В HTML диалог подтверждения не виден. Если он реализован только в JS-коде в виде confirm() браузера — это слабое решение.
Фикс: использовать кастомный modal с явным текстом «Удалить класс X? Это действие необратимо», кнопками «Отмена» / «Удалить».
[LOW] Accessibility: все элементы управления классами доступны только после выбора класса из списка
Состояние «ничего не выбрано» показывает заглушку «Выберите класс» без инструкции о том, что нужно сделать слева. Фикс: добавить стрелку-подсказку или анимацию, указывающую на список классов.
admin.html
[HIGH] Confirmation: смена роли пользователя через inline <select> без подтверждения
<select class="role-select">...</select>
Неосторожный клик меняет роль немедленно. Это деструктивное действие: учитель превращается в студента без возможности отмены.
Фикс: при change события показывать confirmation modal «Изменить роль пользователя X с Y на Z?», только после подтверждения отправлять PATCH-запрос.
[HIGH] Confirmation: кнопка btn-del-q (удалить вопрос) без видимого диалога подтверждения
Удаление вопроса из банка — необратимое действие. Фикс: confirmation modal с предупреждением об необратимости.
[MEDIUM] Error handling: нет отображения ошибок при провале admin-операций
Если PATCH /api/users/:id/role вернул 500, пользователь не узнает об этом. Фикс: показывать toast/alert с текстом ошибки из ответа API.
[LOW] UX: max-height: 0 → max-height: 4000px в session drawer
Анимация открытия непредсказуема по скорости — CSS считает переход от 0 до 4000px, хотя реальная высота 200px.
Фикс: использовать JavaScript для установки точной max-height: element.scrollHeight + 'px'.
test-run.html
[MEDIUM] Feedback: кнопка «Завершить тест» доступна с первого вопроса без минимального порога
Студент может нечаянно нажать и завершить тест, ответив на 0 вопросов. Фикс: показывать предупреждение в confirmation modal: «Ты ответил на X из Y вопросов. Уверен, что хочешь завершить?»
[MEDIUM] Accessibility: <img src="..." alt=""> — пустой атрибут alt у изображений в вопросах
Студенты с нарушениями зрения не получат описание изображения.
Фикс: заполнять alt из поля описания вопроса в БД (например, alt="Изображение к вопросу: ...").
[MEDIUM] Loading: нет серверного сохранения прогресса теста в процессе
SessionStorage работает только в рамках одной вкладки. При сбое браузера прогресс теряется.
Фикс: периодически (каждые 30 сек) отправлять автосохранение через PATCH /api/sessions/:id/autosave с текущими ответами.
[LOW] Feedback: предупреждение таймера только за 120 секунд — мало для коротких тестов
Фикс: добавить предупреждение при достижении 25% оставшегося времени (независимо от абсолютного значения).
test-result.html
[HIGH] Flow: обе кнопки «К тестам» и «В кабинет» ведут на /dashboard
<a href="/dashboard" class="btn-primary">К тестам</a>
<a href="/dashboard" class="btn-ghost">В кабинет</a>
Дублирование кнопок с одинаковым назначением сбивает с толку.
Фикс: «К тестам» → /board (список заданий), «В кабинет» → /dashboard, добавить третью кнопку «Пройти ещё раз» → /test-run?... с теми же параметрами.
[MEDIUM] Navigation: нет возврата к заданию на борде, если тест был из assignment
Если студент пришёл с /board?class=5, после теста нет ссылки «Вернуться к классу».
Фикс: передавать returnUrl параметром и использовать его в кнопке «Назад».
homework.html
[HIGH] Error handling: пустой catch {} при загрузке данных класса в initStudent()
catch {}
Студент видит пустую форму без объяснения причины.
Фикс: заменить на catch(e) { showError('Не удалось загрузить задания. ' + e.message); }.
[HIGH] Forms: при нескольких классах у студента задание всегда подаётся в classes[0].id
Студент в 2+ классах может случайно отправить работу не в тот класс.
Фикс: добавить <select> для выбора класса, если classes.length > 1.
[MEDIUM] Forms: нет клиентской валидации размера файла перед отправкой
Подсказка «до 50 МБ» есть, но проверки нет — пользователь узнаёт об ошибке только после долгой загрузки. Фикс:
if (file.size > 50 * 1024 * 1024) { showError('Файл слишком большой (максимум 50 МБ)'); return; }
[MEDIUM] Error handling: при ошибке загрузки списка работ нет кнопки «Повторить»
Показывается текст «Ошибка загрузки», но нет способа повторить запрос.
Фикс: добавить <button onclick="loadHomeworks()">Повторить</button> в блок с ошибкой.
profile.html
[MEDIUM] Mobile: html, body { height: 100%; overflow: hidden; } сломается при виртуальной клавиатуре
На iOS при фокусе на input виртуальная клавиатура поднимает область просмотра, overflow hidden блокирует прокрутку к полю.
Фикс: убрать overflow: hidden с body, использовать height: 100dvh на layout-контейнере или flex с min-height: 100svh.
[MEDIUM] Security/Forms: форма смены пароля не требует ввода текущего пароля
Любой получивший доступ к разблокированному устройству может сменить пароль.
Фикс: добавить поле «Текущий пароль» (type="password") перед полями нового пароля.
[LOW] Feedback: form-msg появляется, но нет auto-hide через N секунд
Сообщение «Сохранено» остаётся на экране indefinitely.
Фикс: setTimeout(() => formMsg.classList.remove('ok','err'), 4000).
library.html
[MEDIUM] Accessibility/Mobile: кнопки редактирования/удаления папок появляются только при hover
.folder-card-acts { opacity: 0; }
.folder-card:hover .folder-card-acts { opacity: 1; }
На touch-устройствах hover недоступен — кнопки принципиально не видны. Фикс: на мобиле заменить hover на long-press или показывать кнопки через иконку «⋮» (три точки) при тапе на карточку.
[MEDIUM] Confirmation: btn-del (удалить файл/папку) без видимого confirmation modal в HTML
Фикс: добавить modal «Удалить файл/папку X? Это действие необратимо».
[LOW] Navigation: breadcrumb .lib-bc показывает путь, но нет клавиатурной поддержки
Ссылки в breadcrumb должны быть <a href="...">, а не <span onclick="...">.
Фикс: использовать семантические <a> элементы для каждого уровня пути.
analytics.html
[MEDIUM] Loading: нет состояния загрузки для диаграмм Chart.js
Пока данные грузятся — область диаграммы пустая/белая без индикации. Фикс: показывать skeleton-placeholder над canvas до получения данных.
[LOW] Navigation: кнопка «Назад» (an-header-back) ведёт history.back() — может вернуть в сломанное состояние
Фикс: задать конкретный href (/classes или /dashboard) вместо history.back().
gradebook.html
[MEDIUM] Accessibility: при большом числе студентов таблица горизонтально прокручивается, но нет подсказки
Пользователь не знает, что таблица шире экрана. Фикс: добавить fade-gradient на правом краю таблицы, пока есть горизонтальный скролл.
[LOW] Navigation: нет ссылки на профиль студента при клике на имя в таблице
Фикс: сделать имена студентов в первом столбце кликабельными ссылками на /profile?id=....
question-bank.html
[MEDIUM] Confirmation: удаление вопроса (если есть кнопка) без confirmation modal
Фикс: modal с предупреждением «Вопрос может быть использован в активных тестах».
[LOW] Forms: фильтры не сохраняются при переходе к редактированию и обратно
Пользователь теряет выбранные фильтры при навигации.
Фикс: хранить состояние фильтров в URL query params (?subject=bio&type=mc).
live-quiz.html
[HIGH] Confirmation: кнопка btn-end (завершить сессию) без подтверждения
Нажатие заканчивает живую викторину для всех участников мгновенно. Фикс: modal «Завершить сессию? Все участники будут автоматически переведены на экран результатов».
[MEDIUM] Feedback: нет отображения числа подключённых участников в реальном времени
Учитель не видит, сколько студентов уже подключились перед стартом. Фикс: показывать счётчик «X участников подключились» с обновлением через polling/WebSocket.
course.html
[LOW] Navigation: кнопка «Записаться» / «Продолжить» не показывает состояние записи до загрузки
Кнопка мерцает при загрузке (меняет текст после JS-инициализации). Фикс: рендерить кнопку в скрытом/skeleton-состоянии до получения данных о статусе записи.
lesson.html
[MEDIUM] Content: inline quiz не имеет кнопки «Попробовать снова»
После неверного ответа опция подсвечивается красным, но повторить нельзя. Фикс: добавить кнопку «Ещё раз» после показа правильного ответа.
[MEDIUM] Layout: max-height: 600px для accordion-блоков — длинный контент обрежется
.accordion-body { max-height: 600px; overflow: hidden; }
Фикс: убрать max-height, использовать JS element.style.maxHeight = element.scrollHeight + 'px' для анимации.
[LOW] Navigation: оглавление (TOC) скрыто при ширине < 1100px — нет альтернативного доступа
На планшетах нет способа перейти к нужному разделу без ручной прокрутки. Фикс: добавить кнопку «Оглавление» в topbar, открывающую TOC как drawer/dropdown на мобиле.
pet.html
[LOW] Accessibility: интерактивная анимация дыхания без возможности приостановки
Постоянно движущийся элемент может вызывать дискомфорт у пользователей с вестибулярными нарушениями.
Фикс: соблюдать prefers-reduced-motion: @media (prefers-reduced-motion: reduce) { .breath-anim { animation: none; } }.
red-book.html
[MEDIUM] Consistency: доступ через href="/red-book.html" с расширением
Все остальные страницы доступны без .html. Эта единственная исключение.
Фикс: добавить роут GET /red-book → serve red-book.html. Исправить ссылку в сайдбарах всех страниц.
[LOW] Navigation: пункты подменю Red Book (red-book-biomes, red-book-ecosystem, red-book-games) видны в сайдбаре только на страницах Red Book
Пользователь не знает о подразделах, пока не зайдёт в Red Book.
Фикс: допустимо (паттерн контекстного сайдбара), но нужен <title> в виде «Красная книга — Биомы» для ориентации (уже есть).
theory.html
[MEDIUM] Navigation/Roles: кнопка «Создать курс» (btn-new-course) видна в заголовке для всех
Студент видит кнопку действия, недоступного ему по роли.
Фикс: скрывать кнопку до JS role-check (style="display:none" в HTML, показывать только для teacher/admin).
404.html
[LOW] Navigation: кнопка «Назад» использует history.back()
Если 404 открыт по прямой ссылке или из внешнего источника — history.back() закроет вкладку или уведёт на внешний сайт.
Фикс: добавить if (history.length > 1) history.back(); else location.href = '/dashboard';.
403.html
[LOW] UX: три кнопки подряд — «На главную», «Назад», «Войти» — могут запутать незалогиненного пользователя
Если пользователь не залогинен, «На главную» тоже приведёт к редиректу на login.
Фикс: проверять авторизацию через LS.isLoggedIn() и показывать только релевантные кнопки: для залогиненных — «На главную» + «Назад», для незалогиненных — только «Войти».
500.html
[LOW] Feedback: «Мы уже в курсе» — вводящий в заблуждение текст
На статических страницах ошибок нет механизма автоматической отправки отчёта. Фраза ложная. Фикс: заменить на «Попробуй обновить страницу или вернись позже».
biochem.html и семейство (biochem-library, biochem-pathways, biochem-reactions, biochem-properties)
[MEDIUM] Mobile: body { overflow: hidden; } + canvas-интерфейс непригоден для мобиле
Молекулярный конструктор и аналогичные страницы требуют мышь для взаимодействия с canvas. Фикс: добавить предупреждение «Для работы рекомендуется компьютер» при ширине < 768px, или реализовать touch-управление (pinch-to-zoom, tap-to-place).
[MEDIUM] Navigation: нет кнопки «Сохранить молекулу» с очевидным расположением
Действия в правой панели (bp-btn) мелкие и не имеют подписей при сжатии панели.
Фикс: добавить явную primary-кнопку «Сохранить в библиотеку» в тулбар.
[LOW] Accessibility: canvas-элементы недоступны для скринридеров
<canvas id="mol-canvas"> без aria-label и без fallback-контента.
Фикс: <canvas aria-label="Молекулярный конструктор" role="img">Интерактивный редактор молекул</canvas>.
lesson-editor.html
[MEDIUM] Forms: нет автосохранения (видна статусная строка .etb-status, но нет таймера автосохранения)
Потеря введённого контента при случайном закрытии вкладки.
Фикс: setInterval(() => saveDraft(), 30000) + обработчик beforeunload при несохранённых изменениях.
[MEDIUM] Navigation/Confirmation: кнопка «Назад» (etb-back) ведёт без проверки несохранённых изменений
Фикс: перехватить клик, если isDirty, показать: «Есть несохранённые изменения. Уйти?».
[LOW] Accessibility: редактор использует contenteditable (предположительно) без ARIA-ролей
Фикс: role="textbox" aria-multiline="true" aria-label="Содержимое урока".
collection.html / collection-rb.html
[LOW] Empty state: нет состояния пустой коллекции
Если у пользователя нет элементов в коллекции, нет поясняющего empty-state.
Фикс: добавить .rich-empty с иллюстрацией и текстом «Коллекция пуста — открывай карточки видов, чтобы пополнять её».
red-book-biomes.html / red-book-ecosystem.html / red-book-games.html
[LOW] Mobile: canvas-страницы с height: 100vh; overflow: hidden некорректно ведут себя на мобиле
Фикс: использовать height: 100dvh для корректного учёта браузерной панели на мобиле.
[LOW] Navigation: кнопка «Назад» присутствует в topbar как eco-back — хорошо, но нет breadcrumb
Пользователь не видит, где он в иерархии Red Book. Фикс: добавить breadcrumb «Красная книга > Экосистемы».
Кросс-страничные (системные) проблемы
[HIGH] Navigation: ссылка /red-book.html с расширением во всех сайдбарах
Затрагивает: board.html, classes.html, homework.html, library.html, profile.html, dashboard.html и все остальные страницы с общим сайдбаром.
Фикс: добавить роут в Express: app.get('/red-book', (req,res) => res.sendFile('red-book.html', {root: 'frontend'})), изменить все ссылки.
[MEDIUM] Navigation: «скрытые» инструментальные страницы недостижимы из навигации
Следующие страницы не имеют ссылок в основном сайдбаре:
/analytics— аналитика успеваемости/gradebook— журнал оценок/question-bank— банк вопросов/live-quiz— живая викторина/lesson-editor— редактор уроков/flashcards— карточки SRS/crossword,/hangman— игры/knowledge-map— карта знаний/lab— лаборатория симуляций/pet— виртуальный питомец
Пользователи находят их только если знают прямой URL.
Фикс: сгруппировать в сайдбаре или добавить «хаб» страницу с плитками всех инструментов (например, /tools).
[MEDIUM] Consistency: CDN Lucide загружается с разных источников
- Большинство файлов:
https://cdn.jsdelivr.net/npm/lucide@0.469.0/dist/umd/lucide.min.js biochem-pathways.html,biochem-reactions.html:https://unpkg.com/lucide@0.469.0/...
Фикс: унифицировать на один CDN (jsDelivr предпочтительнее по производительности).
[MEDIUM] Accessibility: нет aria-current="page" на активных пунктах сайдбара
.sb-link.active ставится через JS, но без ARIA-атрибута скринридеры не объявляют текущую страницу.
Фикс: при активации добавлять link.setAttribute('aria-current', 'page').
[LOW] Mobile: js/mobile.js не подключён к инструментальным страницам
Страницы analytics, gradebook, question-bank, live-quiz и др. не имеют моб-бара, их нельзя нормально открыть на мобиле.
Фикс: добавить <script src="/js/mobile.js"></script> перед </body> во всех страницах с .app-layout.
[LOW] Consistency: страницы biochem-семейства используют data-lucide без вызова lucide.createIcons()
Если JS lucide загружен defer, иконки не инициализируются до вызова функции.
Фикс: убедиться, что lucide.createIcons() вызывается после загрузки DOM во всех таких страницах.
[LOW] Accessibility: большинство модальных окон (confirm-overlay, modal) не управляют фокусом
При открытии modal фокус остаётся на кнопке открытия, а не перемещается внутрь. Нет aria-modal="true", нет role="dialog", нет aria-labelledby.
Фикс: при открытии modal: modal.setAttribute('aria-modal', 'true'); modal.setAttribute('role', 'dialog'); firstFocusable.focus();. Trap focus внутри пока открыт.
Статистика проблем
| Серьёзность | Количество |
|---|---|
| HIGH | 10 |
| MEDIUM | 32 |
| LOW | 22 |
| Итого | 64 |
Приоритетный план исправлений
Спринт 1 (критические, 1–2 дня)
board.html: исправить href кнопки «Перейти» →/test-run?...login.html: редирект после регистрации по ролиhomework.html: убрать пустойcatch {}, добавить обработку ошибокtest-result.html: разделить кнопки на разные destinationadmin.html: добавить confirmation modal на смену роли и удаление вопросаlive-quiz.html: добавить confirmation на завершение сессии
Спринт 2 (важные, 3–5 дней)
- Express-роут
/red-book+ исправить ссылки во всех сайдбарах - Добавить
/js/mobile.jsна все инструментальные страницы profile.html: поле «текущий пароль» + убратьoverflow:hiddenс bodyhomework.html: select класса при нескольких классах + валидация размера файлаlesson-editor.html: автосохранение + beforeunload guard- Скрыть учительские кнопки (
btn-new-cl,btn-new-course) до JS role-check
Спринт 3 (улучшения, 1–2 недели)
- Добавить
aria-current="page"в сайдбаре - ARIA-атрибуты на всех modal/dialog
prefers-reduced-motionна анимации- Хаб-страница
/toolsили группировка инструментов в сайдбаре - Унификация CDN Lucide на jsDelivr
- Сохранение реакций на сервере (board.html)
- Автосохранение прогресса теста на сервере (test-run.html)
- Контекстные empty-state для всех ошибок загрузки с кнопкой «Повторить»