Новый read-only эндпоинт GET /api/classes/:id/outstanding (teacher/admin, ownership):
по каждому ученику класса — его незакрытые задания (классовые + личные от этого учителя)
со статусом не начато / в процессе / на доработке / просрочено и дедлайном. Логика «сдано»
совпадает с /homework (тест — завершён/исчерпаны попытки; учебник — всё прочитано;
загрузка/файл — есть сдача не на доработке). Общий запрос вынесен в assignmentRowsForUser(uid)
— им же теперь питается /assignments/my (поведение не изменилось, +поле created_by).
Фронт (classes.html): вкладка «Долги» в карточке класса — сводка «должников X из Y,
просрочено Z», по каждому должнику карточка со статус-чипами и списком зависших заданий;
бейдж с числом просрочек на вкладке. Удаление прямо из списка: личное → «у ученика»,
классовое → «у всего класса» (через DELETE /assignments/:id, ownership на бэке).
Verified: classOutstanding смоук на живой БД (14 учеников/58 позиций/42 просрочено,
ownership 403/404/admin); node --check; lint:routes 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Скрытие фич в сайдбаре (и kill-switch геймификации) применялось независимо от роли —
у админа тоже пропадали пункты отключённых модулей. Админ управляет модулями, поэтому
должен видеть и открывать всё.
Добавлен admin-override (_isAdminUser, читает getUser() синхронно из localStorage —
работает и на ранней FOUC-инъекции): для админа _applyFeatureCss чистит <style>-скрытие
и снимает .no-gamification; hideDisabledFeatures выходит до любых скрытий/редиректов;
showBoardIfAllowed показывает доску админу всегда. Поведение для student/teacher не
изменилось. Verified vm-смоук на реальном api.js 10/10.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hero-карточка #hc-lab имела href="/lab", но loadLabOfDay меняет его на
/lab?sim=<id> → CSS [href="/lab"] больше не матчит, карточка оставалась видной.
Прячем по стабильному id: #hc-lab/#hc-pet/#hc-read добавлены в FEATURE_WIDGETS
(lab/pet/textbooks). .hero-row переведён на grid auto-fit (minmax 240) — сетка сама
подстраивается под видимые карточки без дыры; syncHeroRow прячет весь ряд, если
карточек не осталось (мобайл-медиазапрос не трогаем — без инлайн-колонок).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ссылки exam-prep (/exam-prep/*) скрывались отдельным async-механизмом (/api/exam-prep/
tracks), не входящим в синхронный кэш-CSS → мелькали на долю секунды после обновления.
Теперь hideDisabledFeatures кэширует точные хрефы скрытых треков в localStorage
(ls_examhide), а _applyFeatureCss добавляет их в инъект-CSS синхронно из кэша на ранней
загрузке (до сборки сайдбара). При включении трека он убирается из кэша → снова виден
(re-apply _applyFeatureCss после свежего fetch). hideEmptySidebarGroups перенесён в конец
hideDisabledFeatures (учитывает скрытие exam-prep).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Пункт «Путеводитель» теперь отключается как остальные модули: ключ sitemap в
вайтлист updateFeatures (backend) + тумблер в admin → фичи (GAME_FEATURES) +
/sitemap,/sitemap.html в FEATURE_HREFS (скрытие из сайдбара + редирект при выкл).
По умолчанию ВКЛ (opt-in disable). Пустая группа схлопнётся авто (hideEmptySidebarGroups).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Когда все пункты группы сайдбара скрыты (фичи отключены / teacher-only у ученика),
оставался висеть пустой заголовок-аккордеон (напр. «Практика и игры»). Добавлена
hideEmptySidebarGroups(): по .sb-group проверяет computed-display пунктов .sb-link
в теле и прячет группу, если ни одного видимого. Зовётся синхронно из sidebar.js
после сборки (по кэш-CSS — без мигания) и в конце hideDisabledFeatures (по свежим
данным; re-show при включении).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Три проблемы UX отключения модулей:
1) Пустой блок флешкарт на дашборде: виджет #w-flashcard — <div>, а скрытие шло
только по [href]. Добавлена карта FEATURE_WIDGETS (флешкарты→#w-flashcard) —
контейнер прячется целиком.
2) Лаборатория не уходила из сайдбара: не было ГЛОБАЛЬНОГО тумблера lab (только
free-student). Добавлен в whitelist updateFeatures + GAME_FEATURES; map уже знал
lab→/lab. Теперь выключение скрывает пункт и редиректит со страницы.
3) Мигание выключенных модулей (FOUC): hideDisabledFeatures асинхронный. Теперь
loadFeatures кэширует /api/features в localStorage, а _applyFeatureCss инъектит
<style id=ls-feat-hide> синхронно из кэша на ранней загрузке (api.js идёт до
sidebar.js) — сайдбар/виджеты строятся уже скрытыми. Геймификация: класс
no-gamification ставится на <html> (раньше body), ls.css правило body.no-gamification
→ .no-gamification (работает и для html, и для body).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1) Доска при feature_board_enabled=0 не пропадала у учителя/админа: showBoardIfAllowed()
зовётся напрямую на ~20 страницах и показывала доску БЕЗ проверки флага, перекрывая
hideDisabledFeatures(). Теперь функция сперва грузит features и при board===false
держит ссылку скрытой (для всех ролей).
2) Добавлен фич-флаг theory: ключ в вайтлист updateFeatures (backend), тумблер «Теория»
в admin → games (GAME_FEATURES), и /theory,/theory.html в map hideDisabledFeatures
(скрытие из сайдбара + редирект с /theory при выключении). По умолчанию ВКЛ (opt-in disable).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
У Квантика не было фиче-флага — его нельзя было выключить, и он всегда висел
в сайдбаре (даже у учеников без класса). Добавлено по образцу остальных игр:
- adminController.updateFeatures: 'quantik' в whitelist (PATCH принимает флаг).
- games.js: пункт «Квантик: Законы Мира» в GAME_FEATURES и FS_FEATURES
(тумблер в админке → Игры; пишет feature_quantik_enabled).
- api.js hideDisabledFeatures: quantik -> ['/quantik','/quantik.html'] (скрытие
из сайдбара при выключении) + '/quantik' в classOnlyHrefs/classOnlyPaths
(скрыт у учеников без класса, как прочие игры).
Миграция не нужна: флаг «неявно включён», пока админ не выключит (features[key]
!== false => включено). Требует Ctrl+F5 (фронт).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Навигация exam-prep не динамическая — пункты прописываются вручную. Добавил
ссылку на модуль ctmath рядом с «Экзамен 9» (группа «Контент»). Поэтому ранее
модуль не появлялся в панели, хотя открывался по прямому адресу.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(quantik-game): фаза 4 — квантовые способности + SR-комнаты
Глава-созвездие quantum (L12–L16) и фирменные механики — всё через
безопасную модель спеки, движок и бэкенд НЕ тронуты (engine touch = 0):
- Суперпозиция: два тела ball+ball2, goal.when требует ОБА (зеркальный
закон). Туннелирование: forbidden-зона wall + fail wall.hit && tunnel<1;
способность тратит энергию → setParam(tunnel,1). Коллапс/прицел: пунктир-
plot предсказанной траектории на паузе.
- Энергия — клиентский ресурс (localStorage quantik-energy, QuantikEnergy).
- SR-комната: мини-сессия повторения флешкарт в модалке (НЕ iframe),
LS.fcStudySession/fcReview; «Знаю/Легко» дают энергию; текст карт
экранируется, картинки — по regex-вайтлисту.
Все 5 уровней проверены на реальном движке (2★ достижимы; суперпозиция
требует оба тела; туннель-гейт блокирует без заряда). npm test 253/8
baseline; lint:routes 0; цепочка разблокировки проходима.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
feat(quantik-game): фаза 1 — оболочка игры + физ-уровень + прогресс (MVP)
Страница /quantik монтирует уровень-спеку в SimEngine (игровой режим: HUD из
Ф0 + слайдеры закона + play/reset), на победу шлёт результат и показывает
экран успеха (звёзды/время/попытки, inline SVG). Уровень phys-artillery-1
как данные (levels.js): гравитация + запуск тела из угла/скорости, портал,
бонус-звезда. Бэкенд: миграция 076 game_progress (UNIQUE user+level),
/api/game/progress (GET свой / POST upsert best time/stars, attempts++,
auth-only, валидация входа), клиент LS.gameProgress*, пункт сайдбара.
game.test.js 13/13; npm test 251 pass/8 baseline; lint:routes 0.
Уровень проверен на реальном интеграторе (311 выигрышных комбо, 31 на 3★).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
#5 rate-limit (byUser) на дорогих LLM-эндпоинтах: /assistant/ask (20/мин),
/assistant/flashcards (10/мин), /imggen (20/мин) — поверх cooldown/дневного
лимита. Защита от «сжигания» бюджета провайдера одним аккаунтом.
#6 SSE больше не таскает JWT в URL: добавлен authed /notifications/stream-ticket
(одноразовый тикет, TTL 30с), клиент берёт тикет заголовком и подключается
с ?ticket=. ?token= оставлен как временный фоллбэк для старых клиентов.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Производный профиль (без LLM): слабые предметы, трудные темы экзамена,
цель/дата, серия — из test_sessions/exam_attempts/exam_user_plan. Подмешивается
в системный промпт → персональные ответы; такие не кэшируются глобально.
Заметки: таблица assistant_memory + фоновый LLM-экстрактор (дросселирован),
дедуп + лимит 15. Панель ученика «Что я о тебе помню» (профиль + заметки,
удаление). Админ-тумблер. API GET/DELETE /assistant/memory (/:id под
authMiddleware, владелец проверяется в хендлере).
Заодно: сверка стабильного baseline route-auth 56→66 (долг от branch-merge,
хук не идёт на merge) — новых незащищённых маршрутов не добавлено.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Новый GET /admin/assistant/models: тянет список моделей провайдера с лимитами
(OpenAI-совместимый /models: context_length+max_completion_tokens+pricing;
нативный Google generativelanguage: inputTokenLimit/outputTokenLimit) и кэширует
лимиты текущей модели на провайдере. Карточка показывает лимиты у ВСЕХ провайдеров
(не только Kilo), для отсутствующих — фоновая авто-подгрузка. В форме — кнопка
«Загрузить модели провайдера» с выбором модели и её лимитами. Так Gemini и любые
новые модели получают лимиты автоматически.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Конфиг стал списком провайдеров (assistant_providers) + активный (assistant_active).
llmConfig берёт активного; providersOrdered — активный первым, затем остальные с
ключом; callLLMFailover перебирает их при 429/сетевой ошибке (второй ключ подхватывает
при исчерпании квоты). Legacy мигрируется в список. Админ-раздел: список провайдеров
(радио-активный, Тест/Изменить/Удалить) + форма с пресетами. Эндпоинты
POST/DELETE /admin/assistant/provider(/:id), POST /admin/assistant/active.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Источники: RAG возвращает sources (slug/§/ref), под ответом ссылка «по учебнику
X, §N» на параграф (миграция 064: section_ref в textbook_chunks; headless-индексатор
его заполняет). Статический индексатор теперь не затирает headless-данные.
- Режим-наставник: переключатель Ответ/Подсказка/Проверить решение в «Спроси»
(mode в ask + промпт); на карточке экзамена кнопка «Подсказка» (mode hint).
- Оценка ответов: лайк/дизлайк под ответом (assistant_feedback) + сводка в админке.
- Утренний бриф на дашборде: «занимался N из 5 дн + план на сегодня».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- RAG: индексатор scripts/index-textbooks.js → textbook_chunks (миграция 063);
ask() подмешивает релевантные куски учебников (LIKE-скоринг). Покрывает
учебники со статическим текстом; JS-рендеримые — через контекст страницы.
Админка: тумблер RAG + кнопка «Переиндексировать» + число фрагментов.
- Кэш ответов (assistant_cache, 7 дней, только «чистые» вопросы без контекста/
истории) + суточный счётчик (assistant_usage: ИИ/кэш/FAQ) в админке.
- Режим учителя: роль в /context, системный промпт для учителей (задания,
план урока, учительские инструменты), подсказки-чипы для учителей.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Ответы модели рендерятся как markdown + формулы KaTeX (ленивая загрузка),
модель просим оформлять формулы в LaTeX $...$.
- «Объясни это»: ask принимает context; кнопки «Объяснить выделенное» (запоминаем
выделение) и «Объяснить/Конспект параграфа» на учебнике.
- Репетитор на экзамене: кнопка «Спросить Квантика» на карточке задания →
Assistant.ask с условием/ответом/решением как контекстом.
- Быстрые действия: «Флешкарты из параграфа» → POST /api/assistant/flashcards
(модель → JSON, починка обрезанного) → колода через существующий API флешкарт.
- Экспорт Assistant.ask(q,context) / explainSelection().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Плавающий помощник на всех страницах (через sidebar.js + inject в учебник):
контекстные подсказки по странице, проактивные напоминания из реальных данных
(домашка с дедлайном, карточки к повторению, серия под угрозой, квест дня),
поздравления (левелап/серия) и панель «Спроси Квантика» (поиск по FAQ + точка
расширения под локальную модель). Консервативно: дневной лимит, кулдауны,
«не показывать», выключатель в профиле. Лицо — pet-sprite, данные — /api/pet.
Бэкенд: миграция 062 (assistant_enabled + assistant_seen, cross-device «видел»),
GET /api/assistant/context, POST seen/dismiss/ask, PATCH settings — гейт фичи 'pet'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST /api/files требует teacher/admin + library.upload — поэтому сохранение
картинок в «Мои материалы» (вырезка области учебника, обрезка доски,
рисунок, аннотация) падало с 403 у учеников и учителей без этого права.
Добавлен auth-only эндпоинт POST /api/files/personal (только картинки,
is_public=1) + LS.uploadMaterialFile. На него переключены board-clip,
material-save, textbook-clip (вырезка области) и рисовалка в my-materials.
Загрузка в учительскую библиотеку (library/lesson-editor) не тронута.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Бэкенд /api/dashboard/activity: per-day агрегация активности по типам (тест/экзамен/карты/уроки/
онлайн/домашка) из 6 таблиц за ~182 дня (раньше карта считала только тесты).
- Карта раскрашивается по доминирующему типу активности + легенда типов; интенсивность/размер по числу.
- Недельный тренд в футере («эта неделя N · +K к прошлой»).
- Тултип и попап по клику показывают разбивку дня по типам.
- Empty-state для новичков (вместо пустой сетки — призыв + CTA). Календарь «Месяц» тоже от всех активностей.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Страница homework.html была доступна только через виджет дашборда. Добавил пункт
в группу «Учебный процесс» (видно всем — ученик выполняет, учитель задаёт).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- POST /api/materials/:id/share {classId|userId} (teacher/admin): создаёт независимую КОПИЮ
материала каждому ученику класса (source_title «Раздатка: <учитель>») + уведомление через SSE.
- /my-materials: кнопка «Раздать» на карточках (видна учителю/админу) → выбор класса.
- Хелпер LS.shareMaterial. На этом план «Мои материалы» (6 фаз) завершён.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Кнопка «В флешкарты» на карточке-заметке: выбор колоды (или новая «Из материалов») →
создание карточки (front=заголовок, back=текст) через существующий API флешкард.
Хелперы fcListDecks/fcCreateDeck/fcAddCard в js/api.js.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- lesson-history.html (страница учителя): подключён board-clip.js, кнопки «К себе»/«Область»
на доске прошлой сессии (обёртки над _wb + _activeSession).
- sidebar.js: пункт «Мои материалы» теперь виден всем (не только ученикам).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Миграция 061: material_collections + student_materials.collection_id (ON DELETE SET NULL) + tags.
- API: CRUD коллекций (/api/materials/collections), GET /materials отдаёт {materials, collections}
со счётчиками; PATCH /materials/:id принимает collection_id/tags. Хелперы в js/api.js.
- /my-materials: бар папок (Все/папки/Без папки/+папка) с фильтром, поиск по тексту, фильтр по типу,
перенос материала в папку (select на карточке), создание/переименование/удаление папок.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- PATCH /api/materials/:id (title, body) с проверкой владельца (@public-by-design) + LS.updateMaterial.
- /my-materials: кнопка «+ Заметка» (личный блокнот с нуля), «Изменить» на карточках
(заголовок; для заметок — и текст) через LS.modal.
- Добавлен план развития «Мои материалы»: plans/my-materials/PLAN.md (6 фаз).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ученик на странице «Мои уроки» может сохранить к себе страницу доски (PNG) и свою заметку
из прошлой онлайн-сессии. Копия хранится у ученика и переживает удаление сессии учителем.
- Миграция 060: student_materials (kind board/note/link/image, denormalized source_title,
source_session_id ON DELETE SET NULL).
- API /api/materials (GET/POST/DELETE, авторизация + проверка владельца) + helpers в js/api.js.
- my-lessons.html: кнопки «К себе» на доске и заметке (Whiteboard.exportBlob → /api/files → saveMaterial).
- Новая страница /my-materials (просмотр/открыть/скачать/удалить) + пункт сайдбара (ученик).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Клиент: listRoles/createRole/updateRoleDef/deleteRole/rolePermissions. Во вкладке
«Доступ · роли» — блок «Конструктор ролей»: создать роль (имя-идентификатор +
название + базовые роли чекбоксами), список кастомных ролей, «Настроить права»
(тогглы по группам через getRolePermissions + setPermission под именем роли),
«Удалить» (возврат пользователей на базу). В списке пользователей выпадающий
список ролей теперь включает optgroup «Кастомные роли» (выбор по custom_role);
listUsers отдаёт custom_role. Phase C (произвольные роли) завершена на ветке.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Миграция 053: user_permissions.expires_at (NULL = бессрочно). Резолвер isEnabled
+ /me + /users/:id игнорируют просроченные оверрайды (наследуют роль); seedDefaults
чистит просроченные строки. setUserPermission принимает days → выдаёт право на
срок (datetime('now','+N days')). API отдаёт expiresAt. Клиент: setUserPermission(...,days).
В модалке прав пользователя — бейдж «до ДАТА» + кнопка «врем.» (выдать на N дней).
Тест: срок хранится/отдаётся, просроченное игнорируется и вычищается. Backend pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PRESETS (student): «Полный доступ», «Режим фокуса» (без магазина/испытаний),
«Ограниченный» (+ без лаборатории), «Сбросить к стандарту роли». GET
/api/permissions/presets + POST /api/permissions/class/:id/preset (admin).
Рефактор: общий applyPermsToClass() (карта key→1/0/inherit) — его используют и
bulk, и preset. В блоке «Массово по классу» — кнопки пресетов (с подтверждением).
Тест: список + применение focus/reset + валидация. Backend pass (3 baseline-Auth).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST /api/permissions/class/:id/bulk { permission, enabled } (admin, явный
requireRole) — выставляет user_permissions всем ученикам класса (1/0/null=сброс),
точечный token_version bump каждому. Валидация: только студенческие ключи.
Клиент LS.setClassPermission. В админке «Доступ · роли» — блок «Массово по
классу»: выбор класса → у каждого права «включить/выключить всем / сбросить».
Тест: оверрайд всем + сброс + отклонение teacher-ключа. Backend 221 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GET /api/permissions/log (admin-only) — последние изменения ролевых прав (или
?user_id= для личных оверрайдов) из admin_audit_log; читаемый текст («включил
«X» для роли «учитель»») с резолвом меток через registry. Клиент LS.permissionsLog.
Вкладка «Доступ · роли»: блок «История изменений прав ролей» с кнопкой «Показать».
Тест: admin видит записи, не-админу 403. permissions 13/13.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
История: GET /api/access/log (admin-only) — кто/когда открыл/закрыл/сбросил
правило для контента (из admin_audit_log, имена классов/учеников резолвятся).
Клиент LS.accessLog; в режиме «По контенту» — кнопка «История изменений».
Пресет: в режиме «По классу» — «Скопировать доступ из класса [выбор]» (дополняет
текущие правила открытыми правилами класса-источника). Тест: история (admin
видит запись, учителю 403). content-access 13/13.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
В CSS есть только классы .success/.error/.info/.warn, но код принимал любой
type. 7 вызовов LS.toast(...,'warning') и 1 'ok' давали класс без фонового
градиента → белый текст на светлой странице был невидим. Добавлен alias-map
(warning→warn, ok→success, danger/err/fail→error) + fallback неизвестных в
'info', чтобы у toast всегда был фон.
GET /api/access/matrix (классы + карта открытого контента одним запросом,
скоуп учителя). Клиент LS.accessMatrix. Третий режим вкладки «Доступ»:
таблица контент × классы с чекбоксами (правка в один клик) + поиск по
названию (обновляет только tbody — фокус ввода сохраняется), залипающие
заголовки. Тест /api/access смонтирован в харнесс; content-access.test 11/11
(+матрица: учитель видит свои классы и открытый контент, ученику 403).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Мелодию-вызов перевёл с кастомного Web Audio на общий движок звуков LS.sfx:
- длинный вестминстерский бой теперь в sound.js (звук lesson_start);
- api.js лениво подгружает sound.js на любой странице и играет lesson_start
по SSE classroom_started (вместо собственного синтезатора);
- отдельный pref lessonCall + тумблер «Вызов на урок» и кнопка прослушивания
в профиле (Настройки → Звуки); уважает мастер-тумблер и громкость;
- lesson_start выведен из категории classroom (управляется своим тумблером);
- разблокировка AudioContext по первому жесту перенесена в sound.js.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>