Files
Maxim Dolgolyov be4d43105e LearnSpace: full-stack educational whiteboard platform
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>
2026-04-12 10:10:37 +03:00

35 KiB
Raw Permalink Blame History

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: 0max-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 дня)

  1. board.html: исправить href кнопки «Перейти» → /test-run?...
  2. login.html: редирект после регистрации по роли
  3. homework.html: убрать пустой catch {}, добавить обработку ошибок
  4. test-result.html: разделить кнопки на разные destination
  5. admin.html: добавить confirmation modal на смену роли и удаление вопроса
  6. live-quiz.html: добавить confirmation на завершение сессии

Спринт 2 (важные, 3–5 дней)

  1. Express-роут /red-book + исправить ссылки во всех сайдбарах
  2. Добавить /js/mobile.js на все инструментальные страницы
  3. profile.html: поле «текущий пароль» + убрать overflow:hidden с body
  4. homework.html: select класса при нескольких классах + валидация размера файла
  5. lesson-editor.html: автосохранение + beforeunload guard
  6. Скрыть учительские кнопки (btn-new-cl, btn-new-course) до JS role-check

Спринт 3 (улучшения, 1–2 недели)

  1. Добавить aria-current="page" в сайдбаре
  2. ARIA-атрибуты на всех modal/dialog
  3. prefers-reduced-motion на анимации
  4. Хаб-страница /tools или группировка инструментов в сайдбаре
  5. Унификация CDN Lucide на jsDelivr
  6. Сохранение реакций на сервере (board.html)
  7. Автосохранение прогресса теста на сервере (test-run.html)
  8. Контекстные empty-state для всех ошибок загрузки с кнопкой «Повторить»