Раньше: 3 отдельные страницы со своими сайдбарами, header'ами и
скриптами. /textbook-progress был доступен только через кнопку в углу,
/admin-textbooks — только по прямому URL.
Теперь: одна страница /textbooks с тремя вкладками:
• Каталог (все)
• Прогресс класса (учитель/админ)
• Управление (только админ)
URL hash routing: /textbooks#progress, /textbooks#manage. Lazy-init
для каждой вкладки (грузится при первом клике).
Старые страницы превращены в 312-байтные redirect-стабы для
сохранения старых ссылок и закладок:
/textbook-progress → /textbooks#progress
/admin-textbooks → /textbooks#manage
Effect:
- Один header, один сайдбар-load, одна загрузка api.js/sidebar.js
- HTML-страниц сокращено на ~530 строк (textbook-progress.html был
248 строк, admin-textbooks.html — 219; сейчас ~10 каждая)
- /textbooks.html: 467 → 945 строк (+478, поглотил функционал двух
страниц с собственными стилями)
- Чистый UX: всё про учебники в одном месте, переключение
мгновенное (нет полной перезагрузки страницы)
Глава 4 — добавлен раздел 4.4 «Кому: класс / ученик / личный список»:
- Покрывает все 4 режима выпадашки «Кому» при создании задания
- Объясняет связь с «Моими учениками» (Глава 13)
Глава 11 — Учебники (новая):
11.1 Каталог: Шиманович химия 9, Исаченкова физика 9
11.2 Чтение и отметки «Прочитано», закладки выделением текста
11.3 Назначение чтения как ДЗ (классу / ученику, диапазоны §)
11.4 Учительский прогресс класса по учебнику
Глава 12 — Экзамен 9 класс (новая):
12.1 80 вариантов, тёмная сетка с прогрессом
12.2 Импорт нечётных в банк, назначение как ДЗ
+ объяснение «почему чётные — только для просмотра»
Глава 13 — Мои ученики (новая):
13.1 Когда нужно: репетиторство, доп-занятия, подготовка к ЦТ
13.2 Добавить по email (если ученика нет — ссылка на регистрацию)
13.3 Назначения — через /textbooks и в общем поиске
13.4 Удаление (задания не удаляются), счётчик заданий
TOC обновлён: CHAPTERS массив с 10 → 13 глав, счётчик «0 из 13».
Все навигационные кнопки prev/next перепрошиты под новую цепочку:
ch-10 → ch-11 → ch-12 → ch-13 → ch-1 (вернуться к началу).
Проверка: 13 chapters with matching id="ch-N", 40 sections с id=s-N-M,
все ссылки в CHAPTERS массиве соответствуют HTML-якорям.
feat: teacher_students — назначения ученикам без класса
Новая модель «Мои ученики» — учитель связывает с собой учеников
независимо от классов (репетиторский сценарий).
Backend:
- Таблица teacher_students (teacher_id, student_id, added_at, note)
+ индекс на student_id для обратного поиска
- GET/POST/PATCH/DELETE /api/teacher-students — управление списком
- Добавление по email с проверкой роли student/free_student
- Уведомление ученику при добавлении
- createDirectAssignment: проверка inClass расширена до
inClass OR (teacher_id, student_id) в teacher_students
- listStudents (/api/classes/students): возвращает объединение
учеников из классов + из teacher_students. Это автоматически
обновляет student-picker в /textbooks без правок UI.
Frontend:
- /my-students — таблица личных учеников + форма добавления
по email + заметка + счётчик созданных заданий
- Сайдбар: пункт «Мои ученики» (user-plus, только для учителей)
Миграция 006_teacher_students.sql.
Что работает end-to-end:
- Добавить ученика на /my-students
- Открыть /textbooks → «Назначить» → «Ученику» → ученик ищется
в общем списке (классовые + личные)
- Создаётся запись в assignments с user_id, видна ученику на
дашборде с пометкой «Личное задание»
@
A1 — карточка ДЗ-чтения у ученика на /dashboard:
- Новая ветка в buildAssignCard для assignments с textbook_id
- Прогресс-бар «X из Y §», цвет берётся из textbook.color
- Кнопка «Открыть / Продолжить» с deep-link на первый требуемый параграф
- В classify(): textbook_all_read → done, deadline → overdue
A2 — авто-проверка выполнения:
- При POST /:slug/progress с mark_read: проверяются активные textbook-assignments
- Если все требуемые § прочитаны → INSERT в assignment_completion
- SSE-уведомление учителю «Ученик завершил чтение: <title>»
- myAssignments возвращает completed_at и textbook_all_read
A3 — учительский UI прогресса класса:
- Новая страница /textbook-progress (учитель/админ)
- Селекторы «учебник × класс» → таблица учеников с прогрессом
- Сортировка по количеству прочитанного, дата last_at
- Кнопка «Прогресс класса» добавлена в /textbooks (видна учителям)
B4 — admin-UI управления учебниками:
- /admin-textbooks (только admin) — таблица всех учебников
- Inline-редактирование title/author, тоггл is_active
- Колонка «Читателей» (count из textbook_progress)
- Endpoints: GET /api/textbooks/admin/all, PATCH /admin/:id
C7 — закладки/заметки внутри учебника:
- Таблица textbook_bookmarks (user, textbook, para, text, note, color)
- API: GET/POST/PATCH/DELETE для CRUD закладок
- В tracker: при выделении текста (8-400 симв) появляется плавающая «+ Закладка»
- Кнопка-иконка в overlay top-left открывает панель «Мои закладки»
- Хранится paragraph-якорь, цвет, заметка, кнопка удалить
Назначение ученику (в дополнение к классу):
- В модалке /textbooks — переключатель «Классу / Ученику»
- Поиск ученика по имени/email через /api/classes/students
- Submit использует POST /api/assignments (createDirectAssignment)
- createDirectAssignment расширен textbook_slug + textbook_paragraphs
- Учитель может назначать только ученикам своих классов
myAssignments расширен: возвращает textbook fields + post-process
считает textbook_required_count, textbook_read_count, textbook_all_read.
Deep-link поддержка: /textbook/<slug>#pN в tracker.js — на load и hashchange
вызывает setParaTab(pN) (нативная функция учебника).
Миграция 005: assignment_completion + textbook_bookmarks + индексы.
Фаза 1 — структура и каталог:
- frontend/textbooks/chemistry_9.html (Шиманович, 60 §) + physics_9.html (Исаченкова, 38 §)
- frontend/textbooks.html — каталог в стиле LearnSpace (карточки с обложками)
- Маршруты: /textbooks (каталог), /textbook/<slug> (полноэкранный учебник)
- Сайдбар: пункт «Учебники» (book-open-text)
- Feature flag feature_textbooks_enabled, hideDisabledFeatures map
Фаза 2 — прогресс в localStorage + UI чтения:
- frontend/js/textbook-tracker.js — инжектится в каждый учебник:
- «← Учебники» overlay-кнопка (top-left, semi-transparent)
- «Прочитано» чекбокс рядом с каждым §-заголовком
- Зелёный dot на pill уже прочитанных параграфов
- Авто-открытие последнего параграфа при возврате
- Каталог показывает прогресс-бар «X из Y прочитано» + кнопку «Продолжить»
Фаза 3 — серверный прогресс + назначение чтения как ДЗ:
- Таблица textbooks (slug, subject, grade, title, author, color, ...)
- Таблица textbook_progress (user_id, textbook_id, JSON read[], last_para)
- Колонки assignments.textbook_id + textbook_paragraphs
- API: GET /api/textbooks (с прогрессом), GET /:slug, POST /:slug/progress,
GET /:slug/class-progress (учитель)
- tracker.js синхронизирует прогресс через POST /progress (если залогинен)
- На каталоге у учителей кнопка «Назначить чтение» — модалка с выбором
классов + параграфы («1-5» или «1,3,5») + deadline
- bulkCreateAssignment расширен: принимает textbook_slug, резолвит в id
Миграция 004 идемпотентная; сиды двух учебников включены.
Импорт 40 нечётных вариантов (v01, v03, ..., v79) в банк вопросов:
- 400 questions с allow_html=1, source_type='экзамен 9', year=2025
- 540 options (single-choice) + correct_text (short_answer)
- 40 tests (по 1 на вариант), title="Экзамен 9 — Вариант N"
- exam9_variant_tests маппинг для назначения
Назначение варианта как ДЗ на /exam9 (для учителей/админов):
- Кнопка «Назначить как ДЗ» под заголовком варианта (только если test_id есть)
- Модалка выбора классов + опциональный deadline
- POST /api/assignments/bulk с test_id из exam9_variant_tests
Поддержка HTML/SVG в вопросах банка через флаг questions.allow_html:
- Миграция 003: ALTER TABLE questions ADD COLUMN allow_html
- sessionController: SELECT возвращают allow_html и image
- test-run.html: рендер q.text и opt.text как HTML при allow_html=1
- test-result.html: то же для explanation и opt.text
- KaTeX: добавлены $...$ и $$...$$ delimiters в обеих страницах
Бонус-фикс: bulkSchema требовал class_id (single), контроллер ждёт
class_ids (array). Существующий вызов из classes.html был сломан;
исправлено вместе.
Команда: node backend/scripts/import-exam9.js (--all для всех 80)
Новый отдельный модуль /exam9 в стиле LearnSpace:
- 80 вариантов × 10 заданий = 800 задач с разбором (KaTeX + SVG)
- Сайдбар: пункт «Экзамен 9 класс» (clipboard-check)
- Feature flag: feature_exam9_enabled (мигр. 002)
- Видим всем авторизованным; рендер на стороне клиента
- Прогресс в localStorage: подсветка вариантов (done/partial)
- Возобновление последнего варианта при возврате
Структура:
frontend/exam9.html — страница (LearnSpace layout)
frontend/js/exam9/app.js — рендерер
frontend/js/exam9/variants/ — 80 файлов с данными
frontend/img/exam9/ — 22 PNG/JPG фигур заданий
Картинки путей _tmp/ → /img/exam9/ переписаны автоматически.
Все маршруты проверены: 200 OK на /exam9, /js/exam9/*, /img/exam9/*.
lab.html: 9273 → 5180 lines (-44%)
frontend/js/labs/lab-init.js: new file, 4098 lines
All 34 _open*() functions + sim instance vars + openSim/closeSim/
_addTouchSupport/_simShow + THEORY data extracted to lab-init.js.
Shared globals (FN_COLORS, ALL_SIM_BODIES, ALL_CTRL_BARS, sim vars)
converted from const/let to var for cross-script accessibility.
Three vars in lab.html made global: _disabledSimIds, _theoryOpen, _embedMode.
Load order preserved: lab-init.js added before main <script> block.
Tokens in URL leak through proxy access logs, browser history and
Referer headers. Now: WS opens unauthenticated, client sends
{type:'auth', token} as first message; server responds with
{type:'auth_ok'} and starts normal message processing.
5-second timeout closes any unauthenticated connection.
Frontend queues session join until auth_ok received.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Исправлены ключи сортировки: oldest→date_asc, easy→diff_asc, hard→diff_desc
- loadTopics() теперь использует /api/subjects/:slug/topics вместо
запроса 200 вопросов (не хватало для 400+ вопросов)
- Добавлен onchange для select#f-topic (без него выбор темы не срабатывал)
## P0
- admin.html:2608, red-book-ecosystem.html:489-495 — XSS: u.name/node.name_ru/description обернуты в LS.esc()
- classController.js getAnnouncements — добавлена проверка teacher_id (B14: учитель A не может читать объявления класса B)
## P1 — auth & validation
- authController.js — минимум пароля 6→8 символов (register + change password + login.html)
- gamificationController adminAward — валидация max XP/coins (1M), Number coercion
- shopController adminAwardCoins — валидация max + проверка changes>0
## P1 — race conditions
- petController.buyBg — atomic UPDATE WHERE coins>=? (race-safe)
- shopController.purchaseItem — atomic conditional UPDATE
- liveController — добавлен question_id в live_answers (миграция с пересозданием таблицы), история ответов сохраняется при смене вопроса учителем
- ws-server: invalidateDrawCache экспортирован, classroomController grant/revoke вызывают его → permission revoke применяется мгновенно (раньше до 10s stale)
## P1 — rate limits & retry
- rateLimit middleware: новый параметр byUser=true (использует req.user.id вместо IP — не блокирует пользователей за NAT)
- routes/classroom.js: reactionLimiter (15/5s) на /chat/:msgId/react, handLimiter (5/5s) на raise/lower hand
- api.js sendAnswer — retry 3x с exp backoff (300/1200/2700ms), не повторяет на 4xx (F5)
## P1 — performance
- classroomController.getStrokes — LIMIT 5000 + флаг hasMore (защита от OOM на 10K+ strokes)
- whiteboard.js _liveStrokes — TTL 1.5s на каждый live preview (auto-cleanup при крашe ремоут юзера)
## Infrastructure
- config.js: TURN_URL/USER/PASS env vars
- server.js: GET /api/ice-servers возвращает STUN + опциональный TURN из env
- classroom-rtc.js: фетчит /api/ice-servers вместо хардкода (поддержка TURN для NAT/CGNAT школьных сетей)
- .env.example: документация TURN
- db.js: PRAGMA synchronous=NORMAL (5x быстрее с WAL), cache_size 16MB, temp_store=MEMORY
- ws-server.js closeAll() + server.js shutdown — graceful WS shutdown при SIGTERM
## False positives (не баги, агенты ошиблись)
- assignmentController FK на tests — на самом деле users (migrate.js:317-318)
- .env в git — gitignore корректно исключает
- admin.html без requireAuth — есть LS.initPage() который вызывает requireAuth
- submissionsController IDOR — обе ручки уже проверяют teacher_id
- screenSender = null inside try/catch — на самом деле снаружи
- SSE без backoff — есть exponential 2s→30s
- sessionController NOT IN на пустом массиве — есть guard usedIds.length>0
- getChat без LIMIT — есть LIMIT 100/200
- trust proxy — установлен на server.js:105
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Безопасность:
- tests/🆔 скрыть is_correct и explanation для студентов (P0)
- SQL injection: limit/offset через placeholder вместо template literal
- Stored XSS: stripTags для lesson comments, flashcards, redBook sightings
- profile.html: escape e.message в showMsg (XSS через server error)
- attachment_url: валидация только /uploads/* путей
- requestId: генерировать UUID сервером, не доверять клиенту
- register: скрыть token_version из ответа
Надёжность:
- register: обработка UNIQUE constraint race condition
- pet buyBg: re-check баланса внутри транзакции
- DB errors: скрыть e.message в testController/questionController/courseController
- preferences: лимит 50KB на размер JSON
UX:
- board.html: debounce 250ms на search input
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- draw_permitted: emit→emitToUser (WS доставка вместо SSE-only)
- raised hands: убран in-memory Map, единый источник — таблица classroom_hands
- endSession: очистка classroom_hands при завершении сессии
- VALID_THEMES: исправлен список (добавлен corkboard, убраны dark/grid/dots)
- XSS: crLoadOnlineStudents — inline onclick заменён на data-* + addEventListener
- signal(): проверка что target_user_id является участником сессии
- WS rate-limit: 120 msg/sec per connection
- invalidateSession при join/leave для мгновенной видимости новых участников
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New 'Настройки' tab in profile.html between Закладки and Безопасность
- Sound section: global enable toggle, volume slider, per-category toggles (ui/classroom/gamification/quiz)
- Test buttons to preview each sound category
- Appearance section: animation toggle, push notifications toggle
- All sound prefs persist via LS.sfx localStorage (ls_sfx key)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Добавлены фигуры: усечённая пирамида, октаэдр, икосаэдр, додекаэдр
- Формулы V, S, r_вп, R_оп для всех новых фигур
- Инструмент '∠ скрещ. прям.' — угол и расстояние между скрещивающимися прямыми (4 клика)
- Для икосаэдра/додекаэдра — THREE.IcosahedronGeometry/DodecahedronGeometry с извлечением рёбер
- Вписанная/описанная сфера поддерживает октаэдр, икосаэдр, додекаэдр
- Параметр n добавлен для пирамиды
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Онлайн-урок:
- Кнопка «Рисовать» в баре симуляции (только учителю)
- При активации: холст доски показывается поверх iframe (z-index), фон прозрачный
- Учитель рисует прямо поверх симуляции обычными инструментами
- Студенты видят то же самое через SSE (classroom_sim_annotate)
- Выход из режима → кнопка «Вернуться к симуляции»
Планиметрия (bugfix):
- arcmark теперь рисуется всегда (не зависит от showAngles)
- altitude/median: 1 клик на вершину треугольника (авто-находит противоположную сторону)
- centroid/orthocenter: 1 клик внутри/на треугольник
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- arcmark: рисуется всегда (не только при showAngles=true)
- altitude/median: 1 клик на вершину треугольника (авто-определение)
- centroid/orthocenter: 1 клик внутри/на треугольник
- thales: 3 клика O, A, B → A'B' параллельно AB, коэф. k
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Фаза 7:
- altitude: высота (клик на сторону → клик вершина → foot + отрезок + прямой угол)
- median: медиана (3 клика A,B,C → midpoint + отрезок)
- centroid: 3 клика → 3 медианы + точка G (centroid constr)
- orthocenter: 3 клика → 3 высоты + точка H (orthocenter constr + altitude_foot constr)
- gOrthocenter() math function
- Прямые углы для altitude_foot в _drawAngleMeasures
- Исправлен баг onHintChange: передавался boolean вместо numeric phase
Фаза 8:
- tick tool: метки равных сторон на отрезках и сторонах полигонов (1–3 штриха)
- arcmark tool: метки равных углов на вершинах полигонов (1–3 дуги)
- _drawTickMark(), sideMarks[], angleMarks[] на полигонах
- Новая секция «Метки» в панели инструментов
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6.1 Стороны полигонов теперь выбираются как опорные линии (_hitTestLine),
через виртуальные сегменты (virtual:true, polyId). Cascade-удаление
исправлено на BFS (transitive deps). Теперь можно строить высоты треугольников.
6.2 Прямой угол (квадратный маркер) рисуется для всех foot-конструкций
в _drawAngleMeasures, независимо от полигона.
6.3 Удаление отдельных объектов: onDeleteRequest callback, диалог
«Только этот» (derived-точки → свободные) / «Со всеми зависимыми»
(cascade) / «Отмена». CSS-панель .geo-del-confirm поверх canvas.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- gTangentPoints(O, P, r): касательные через полярно-полярную точку M=O+v*r²/d, h=r√(d²-r²)/d
- tangent: 2 derived_line (which=0/1) из внешней точки к окружности; оба пересчитываются
при движении точки или изменении радиуса/центра; _pendingCircRef хранит окружность-источник
- translate: derived point P'=P+(B-A) по вектору AB; 3-фазный ввод с onHintChange(tool,2/3)
- _hitTestCircle(): найти окружность под курсором (HIT=12px)
- _drawLineRefHighlight(): расширен для circle (рисует дугу подсветки)
- _pendingCircRef очищается в setTool()
- lab.html: кнопки Симметрия/Перенос/Касательные, _GEO_PHASE_HINTS словарь,
_geoShowHint(name, phase) принимает числовой phase вместо boolean
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GeoEngine: _dependsOn/recompute для constr='reflect' и 'ngon_vertex'
- reflect: производная точка-отражение (P'=2·foot-P), зависит от axis+srcPt
- ngon: правильный n-угольник по центру и вершине; вершины v1..vn-1 = derived
points (constr='ngon_vertex', хранят srcCenter/srcVertex/k/n); при движении
центра/вершины все вершины автоматически пересчитываются
- GeoSim: _ngonSides=6, setNgonSides(n), инструменты 'reflect'/'ngon' в _handleToolClick
- lab.html: кнопки Симметрия и n-угольник, +/- контроллер сторон, хинты Phase 2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Интерактивная карта всех модулей платформы:
- Hero с анимированными blob-элементами и gradient-заголовком
- Sticky filter bar (Учёба / Практика / Игры / Личное)
- Маршрут новичка (Дашборд → Теория → Задания)
- 16 карточек модулей с accent-цветами по категориям
- Staggered entrance через IntersectionObserver
- Тёмная тема, мобильная адаптация
- Ссылка в sidebar.js
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>