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>
- Add js/sidebar.js: generates full sidebar HTML into #app-sidebar,
handles role-based visibility, active link (with prefix matching),
toggle wiring, collapsed state, board/features/notif init
- Replace <aside class="sidebar">...</aside> with <aside id="app-sidebar">
across all 35 standard-layout pages via scripts/apply-sidebar.js
- Add notifications.js to 5 pages that were missing it
- Fix api.js initPage(): skip toggle re-wiring if data-sb-wired set,
fix active link selector .sb-item → .sb-link
- Remove stale sbl-*/nav-admin/btn-upload-nav getElementById calls
that crashed after sidebar replacement (lab, classes, collection,
crossword, hangman, knowledge-map, library, pet, profile)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Classroom performance:
- WebSocket server (ws-server.js) for low-latency cursor & stroke preview
Replaces HTTP POST per event → eliminates per-message auth overhead
Session member cache (30s TTL) avoids SQLite query per WS message
Fallback to HTTP POST when WS not connected
- Cursor throttle reduced 100ms → 33ms (~30fps)
- Stroke preview throttle reduced 50ms → 20ms
- whiteboard.js: render() is now rAF-gated (_doRender/_rafPending)
Multiple render() calls within one frame collapse into one repaint
document.hidden check — zero CPU when tab is in background
visibilitychange listener restores canvas on tab focus
Guest board:
- guestClassroom.js route: public token-based read-only access
- guest-board.html: name entry + read-only whiteboard + SSE
- SSE: addGuestClient/removeGuestClient/emitToGuests
Screen share picker:
- Discord-style modal with tab switching (screen/window/tab)
- Live video preview before confirming share
- useExistingScreenStream() in ClassroomRTC
Fullscreen exit overlay:
- #cr-fs-exit-overlay button inside cr-board-wrap
- Visible only via CSS :fullscreen selector (touchpad users)
File sharing from library:
- Teacher picks file from library, sends as styled card in chat
- crDownloadLibraryFile() fetches with Bearer auth
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Minimap:
- Auto-shows in bottom-right corner when zoom > 1.05
- Renders full board content at scale (background + all strokes)
- Purple viewport indicator with darkened outer areas
- Click/drag to jump-pan the viewport
- Cleaned up on destroy()
Ruler/protractor property controls:
- Rotation handle (purple ↺) — drag to rotate around origin
- Resize handle (cyan ↔) — drag to change length/radius
- Protractor now supports rotation via ctx.rotate(ov.angle)
- Floating props panel in toolbar: angle° and length/radius inputs
- Panel auto-shows on first click/drag, hides when overlay toggled off
- Canvas-space hit testing with rotation-aware local coordinates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>