Лицо Квантика в шапке чата (PetSprite) меняет настроение по состоянию:
- думает (нейтральное + лёгкая анимация-покачивание asstThink) пока ждём/стримим
- радуется (happy) на готовый ответ; грустит (sad) на ошибку/лимит/«не нашёл»
- ликует (ecstatic) на сгенерированный тест и нарисованную картинку
Вплетено в send/sendNonStream/makeQuiz/drawInChat через setNameFace().
Анимация уважает prefers-reduced-motion. Только frontend.
Серия из 6 фич доработки Квантика завершена (стриминг, контекст урока,
сократический режим, авто-здоровье провайдеров, генерация тестов, живость).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Учитель: режим «Тест в банк» в Квантике — тема/текст превращается ИИ в вопросы
с выбором ответа, ревью в чате (варианты, верный подсвечен, пояснение),
кнопка «Сохранить в банк» (выбор предмета + тема) создаёт их через POST /questions.
Бэкенд: questionsFromText (по образцу flashcardsFromText, надёжный парс JSON
с починкой обрезанного) + роут POST /assistant/questions (requireRole
teacher/admin, fcLimiter). Клиент: LS.assistantQuestions. Виджет: режим quiz
только для учителя + makeQuiz (рендер и сохранение через createQuestion/getSubjects).
Проверено на живом шлюзе: 5 валидных вопросов, верный индекс в диапазоне.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Новый модуль assistant-health.js (по образцу classroom-cleanup): каждые 15 мин
пингует каждого провайдера (pingLLM) → app_settings.assistant_health
{ id:{ok,at,error,ms,fails} }. Авто-понижение: если активный провайдер
не отвечает 2+ раза подряд, а есть здоровый рабочий запасной — автоматически
переключает assistant_active и пишет assistant_failover (баннер «health»).
schedule() из server.js (unref).
Админка: тумблер «Авто-проверка провайдеров», кнопка «Проверить сейчас»
(POST /admin/assistant/health → runHealth), цветной индикатор здоровья на
каждой карточке провайдера (зелёный/красный + время/ошибка в title).
keyless-шлюзы и провайдеры без ключа учтены.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- тумблер учителя «Сократический режим» (/admin#assistant): для УЧЕНИКОВ
Квантик объясняет теорию полно, но конкретные задачи не решает «под ключ» —
даёт метод, первый шаг и наводящий вопрос (assistant_socratic в app_settings)
- авто-анти-чит: явная просьба «сделай за меня / реши моё дз / do my homework»
включает сократический режим даже без тумблера (_CHEAT_RE)
- учителей/админов и режимы hint/check не ограничивает; работает и в /ask, и в стриме
_socraticFor(role,mode,q) + проброс socratic в buildAskMessages. Бэкенд+админ-UI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Квантик теперь знает, где находится ученик:
- pageHint() — лёгкий ситуативный контекст («ученик на странице учебник:
«Физика 7, §12»») подмешивается к ЛЮБОМУ свободному вопросу автоматически
- getPageContext() расширен с учебника на уроки (theory/course/lesson):
«Объяснить этот урок / Конспект урока / Флешкарты из урока»
- метки чипов адаптируются (параграф/урок)
Бэкенд уже принимал context (pageCtx) — правок не потребовалось.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ответ модели «печатается» вживую через SSE поверх POST (fetch-stream,
не EventSource). Бэкенд: callLLMStream (stream:true, парсинг SSE upstream) +
callLLMStreamFailover (failover только до первого куска) + endpoint
POST /assistant/ask/stream (события meta|delta|done; быстрые пути FAQ/кэш/мета
отдаются одним done). buildAskMessages выделен из askModel (DRY).
Клиент: LS.assistantAskStream (fetch-stream + парсер SSE). Виджет: send()
стримит дельты как plain-текст с CSS-кареткой, на done — KaTeX-рендер,
источники, ссылки, оценка. Фоллбэк на sendNonStream (старый путь) если
стриминг недоступен/упал до первого куска. Cache-Control: no-transform
отключает буферизацию compression.
Проверено против живого шлюза: 24 дельты, первый текст ~1.3с, 100% русский.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pollinations (text.pollinations.ai/openai, модель openai) даёт бесплатный
инференс БЕЗ ключа — проверено: 98% чистый русский. Чтобы такой провайдер
считался рабочим (раньше ключ требовался всем, кроме localhost):
- _noKeyNeeded/_aNoKey: localhost ИЛИ pollinations.ai → ключ не обязателен
(используется в providersOrdered, pingLLM, active-check, testAssistant)
- пресет «Pollinations (без ключа)» в ASSISTANT_PRESETS
- бейдж провайдера: «без ключа» (зелёный) вместо «нет ключа» для keyless
Кейд-провайдеры (Kilo/Gemini/HF/…) по-прежнему требуют ключ — затронуты
только URL с pollinations.ai (спуф в пути отвергается).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Кнопка «Сканировать модели» в /admin#assistant: тянет live-список со шлюза
провайдера, отбирает бесплатные чат-модели (музыка/картинки/модерация
отсекаются), прогоняет каждую тест-запросом на русском и показывает отчёт
(новые / исчезнувшие / % кириллицы / скорость). «Применить выбранные»
сохраняет список в app_settings (assistant_kilo_models); хардкод KILO_MODELS
остаётся сидом, есть «Вернуть встроенный список».
Backend: scanModels/probeModel/applyModels (admin-only роуты), _kiloModels()
делает список динамическим. Переиспользует _fetchModels. Клиент: adminAssistantScan/Probe/ApplyModels.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- развёртка: участок кривой [0, α] выделяется ярче (с свечением) —
видно, как угол на окружности «разворачивается» в график
- подпись текущего угла (π/3 и т.п.) на вертикальном маркере, KaTeX
- подписи делений оси X (π/2, π, 3π/2, 2π) — теперь KaTeX-оверлеем
- название функции (y = sin x / cos x / tg x / ctg x) — KaTeX-оверлеем
- _ovLabel: любая LaTeX-команда (\pi, \sin…) теперь рендерится через KaTeX
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Координатный тултип (cos; sin) выносится радиально НАРУЖУ за точку (вдоль луча от центра),
а не просто со смещением — так KaTeX-плашка значений всегда дальше от центральной дуги угла
и её подписи (π/3 и т.п.), наложения нет.
Verified: node --check; смоук — coord-подпись дальше от центра, чем сама точка.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
На <canvas> KaTeX не рисуется (fillText), поэтому подписи, которые были юникод-текстом
(√2/2, координаты точки, π/4, значение на графике), переведены на HTML-оверлей #trig-overlay
поверх холста с KaTeX-рендером и точным позиционированием (transform по CSS-px = координаты
canvas). Переведены: координатная подсказка (cos; sin), бейджи значений sin/cos, метка угла
у дуги, бейдж значения на графике. Подписи-слова sin/cos/tg/ctg и мелкие точки табличных
углов остаются на canvas (не математика / 16 мелких меток).
Механика: _ov/_ovLabel/_ovClearUnused — кэш по ключу (ре-рендер только при смене LaTeX),
KaTeX лишь для дробей/корней, простые числа — текстом (быстро при перетаскивании), неис-
пользованные за кадр подписи прячутся. Старые canvas-методы _badge/_tooltip больше не зовутся.
Verified: node --check; headless-смоук оверлея 12/12 (coord/vsin/vcos/angle/gval создаются,
KaTeX-LaTeX для √2/2 и π/4, позиционирование/плашка, десятичные как текст, скрытие при
выкл. слоя/графика). Эмодзи нет.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Тумблер «Чётность (−α)»: на окружности рисуется зеркальная точка −α (отражение через
ось Ox, пунктир P↔−α) — наглядно нечётность sin и чётность cos. Блок-справка на KaTeX
(строится один раз): sin(−α)=−sin α, cos(−α)=cos α, tg(−α)=−tg α, периоды
T_sin=T_cos=2π, T_tg=T_ctg=π. (Формулы приведения для текущего угла — уже Фаза 2.)
Аддитивно: this.showParity + _drawParity + хук в draw(); glue trigToggleParity;
тумблер + #trig-parity в панели.
Verified: node --check; headless-смоук 9/9 (_drawParity без throw для 30/150/210/300;
toggle строит блок один раз с верными тождествами+периодами, показ/скрытие). Эмодзи нет.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Значения sin/cos/tg/ctg в панели и стат-баре теперь рендерятся KaTeX для дробей/корней
(\tfrac{1}{2}, \tfrac{\sqrt{3}}{2}, …), а простые числа (0, ±1, десятичные) — текстом
(быстро при перетаскивании, без лишних KaTeX-вызовов). Бейдж угла — KaTeX π-доли по
таблице 16 углов (150° = 5π/6, 210° = 7π/6, …) + радианы + котерминальные.
Хелперы: _tex (общий рендер с фолбэком), _angleLatex/_piLabelToLatex (рад → LaTeX π-доли),
setMathVal (KaTeX только для нетривиальных форм). Формулы значений/приведения и уравнений
уже были на KaTeX.
Verified: node --check; headless-смоук 9/10 (10-я — артефакт стаба: в реальном DOM
textContent= очищает прежний innerHTML; логика LaTeX верна). Эмодзи нет.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Режим уравнения fn(x)=a (sin/cos/tg): окружность подсвечивает ВСЕ решения на [0,2π)
(точки + направляющая линия значения), а панель показывает общую формулу через KaTeX:
sin x=a → x=(-1)ⁿ·arcsin a + πn; cos x=a → x=±arccos a + 2πn; tg x=a → x=arctg a + πn.
Для табличных значений главное значение подставляется точно (arcsin½=π/6 и т.п.), для
нетабличных — символьно (\arcsin a). |a|>1 для sin/cos → «нет решений». Список решений
в градусах. setEquation встаёт на первое решение; clearEquation выходит из режима.
Аддитивно: новое поле this.eq + методы setEquation/clearEquation/_drawEquation + хук в draw();
glue trigSetEqFn/trigSolve/trigClearEq/trigEqKey; секция «Уравнение» в панели labs-bodies.
Verified: node --check; headless-смоук 13/13 (решения sin/cos/tg/один/нет; формулы
(-1)ⁿ/±/+πn/none/нетабличное→arcsin) + изолированная отрисовка _drawEquation без throw.
Эмодзи нет.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Блок «Точные значения · приведение» теперь рендерится KaTeX (katex.renderToString,
как в graph.js/_sim_engine), с фолбэком на сырой LaTeX если katex ещё не загрузился.
Добавлен _latexVal (точное значение → LaTeX: \tfrac{1}{2}, \tfrac{\sqrt{3}}{2}, …),
функции \sin/\cos/\operatorname{tg}/\operatorname{ctg}, цвет наследуется от CSS-цвета
контейнера (без \textcolor). Формула приведения и значения — те же (проверено).
Verified: node --check; headless-смоук LaTeX-вывода (дамп + проверки) — 150°=180°−30°
sin=½ cos=−√3/2 tg=−√3/3; 45° без приведения √2/2; 90° tg «не опр.»; 210°=180°+30°;
300°=360°−60°; 137° нетабличный. Эмодзи нет.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
При исследовании выяснилось: Пифагор (sin²+cos²=1, _pythBar) и знаки по четвертям
(_quadSigns) уже рисуются на canvas. Поэтому Фаза 2 даёт главное недостающее по программе —
блок «Точные значения · приведение»: для текущего угла показывает sin/cos/tg/ctg точными
значениями (½, √2/2, √3/2, √3/3, √3) и для нетривиальных четвертей — формулу приведения
к острому углу (напр. 150° = 180°−30°, cos 150° = −cos 30° = −√3/2). Нетабличный угол →
сообщение. Без KaTeX (чистый HTML + готовый форматтер _f), без новых зависимостей.
Verified: node --check; headless-смоук рендера 11/11 (150° приведение+знаки, 45° QI без
головы, 210° QIII tg+, 137° нетабличный). Эмодзи нет.
sec/csc (5-я/6-я функции) — вторичны для школьной программы, отложены (предложу опционально).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
План тренажёра в plans/trig-circle/PLAN.md (всё по теме на окружности, кроме графиков функций).
Фаза 1 (аддитивно к рабочему режиму):
- Ввод угла в градусах (поле + Enter/кнопка) → goToAngle (нормализует, показывает
котерминальность). Подсказка «+360°·k» в бейдже угла.
- Тумблер «График/функции» — скрыть график (тема «функции») → круг на всю ширину
(переиспользует существующий слой graph + _layout).
- Полная сетка табличных углов (16: 0…330°) вместо 8.
- Опорный (острый) угол к оси Ox в выводе (основа формул приведения) + знаки sin/cos/tg
по текущей четверти. stats() расширен полями refAngle/refDeg.
Verified: node --check; headless-смоук (vm + canvas-Proxy) 9/9 — опорный угол 30/150/210→30°,
300→60°, 90→90°, 0→0; знаки по четвертям (II: sin+ cos− tg−; IV: sin− cos+); новые
глобальные glue-функции определены. Эмодзи нет (стрелка — inline SVG .ic, tg-неопр. — em-dash).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Глобальный репортер в api.js (грузится на всех страницах) ловит необработанные JS-ошибки
(window 'error') и rejected-промисы ('unhandledrejection') в браузере пользователя и шлёт
в POST /api/client-errors. Дедуп по сигнатуре + лимит 15/страницу, только для залогиненных,
keepalive, не флудит и сам не падает.
Бэкенд: routes/clientErrors (auth + rate-limit 20/мин на юзера) → clientErrorController
пишет в общий error_log с level='client' (message/stack/route=url/method=kind/user_id),
поля обрезаются. Появляются в существующей админ-вкладке «Ошибки» с бейджем «БРАУЗЕР»
(фиолетовый акцент vs розовый у серверных). Тест client-errors.test.js 5/5.
lint:routes 0; node --check всех файлов.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Раньше «Добавить вопросы» в конструкторе тестов грузил лишь первые 100 вопросов предмета
(дефолтный лимит API), а поиск фильтровал клиентски только эти 100 — для математики
(1753 вопроса) 1653 были недоступны и не находились.
Теперь пикер ходит на сервер (бэкенд уже умеет q/difficulty/type/page): поле поиска
(debounce 300мс) ищет по ВСЕМУ банку предмета; кнопка «Показать ещё» подгружает
страницами по 100 с индикатором «Показано N из total»; добавлены фильтры по сложности
и типу. Поиск/фильтры сохраняются между перерисовками (после добавления вопроса).
Чистый фронтенд (tests.js + CSS в admin.html); бэкенд не тронут. Verified:
backend list q/difficulty/type/paging 8/8; headless-смоук пикера 12/12.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Алерт «Зависла» (in_progress >1ч) вёл на /admin#sessions, но список сессий показывает
ТОЛЬКО completed (getAllSessions: WHERE status='completed') — поэтому зависшей сессии там
не было (симптом: «показывает зависший тест, но в списке его нет»). Теперь «Открыть»
делает deep-link на детали конкретной сессии /admin#sessions/<id> — страница деталей
открывает сессию при любом статусе (getSessionDetail без фильтра по статусу) и позволяет
её посмотреть и удалить.
node --check OK; id присутствует в payload overview (stuckSessions → ts.id).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Любой авторизованный пользователь подаёт пожелание (заголовок, категория, описание);
видит только свои. Админ видит все, фильтрует по статусу, ведёт по статусам
(новое → запланировано → в работе → готово / отклонено) и пишет ответ автору. Автор
получает уведомление при смене статуса (pushNotif).
Бэкенд: миграция 080 (таблица wishes), wishController (list/create/update/remove с
валидацией и whitelist категорий/статусов), routes/wishes (PATCH — только админ, DELETE —
автор«новое»/админ, проверка в хендлере), смонтировано в server.js. Тесты 15/15.
Фронт: страница /wishes (форма + список со статус-бейджами; у админа — фильтры,
смена статуса, ответ, удаление), пункт «Пожелания» в сайдбаре (все роли), фиче-флаг
feature_wishes_enabled (тумблер в админ-модулях + whitelist + FEATURE_HREFS; админ
видит всегда). Клиентские врапперы LS.wish*.
⚠️ Живой БД нужен npm run migrate (080). lint:routes 0; node --check всех файлов + инлайна.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Логика классификации типа задания и статуса «сдано» дублировалась в трёх местах
(dashboard.html, homework.html, assignmentController.js) и начала расходиться. Вынес в
один UMD-модуль frontend/js/assignment-utils.js (грузится и в браузере, и в Node через
require — как svg-sanitize.js): type(a), isDone(a, sub, opts), urgencyScore(a).
Нюанс «сдано» для upload/file параметризован: вид ученика (acceptedOnly) — закрыто только
при принятой сдаче; учитель/обзор долгов — любая сдача не на доработке. Поведение всех трёх
поверхностей сохранено 1:1.
- homework.html: asgnType/asgnDone/urgencyScore → тонкие делегаты в AssignmentUtils.
- dashboard.html: urgencyScore делегирует; classify и upload-ветка buildAssignCard через
AssignmentUtils.type/isDone (заодно корректнее: учебник-ДЗ больше не путается с upload).
- assignmentController: classOutstanding/_assignTypeOf → AssignmentUtils.
Verified: AU-контракт 25/25 (типы, isDone teacher vs acceptedOnly, порядок urgency);
интеграция 8/8 (classOutstanding те же 14 уч./42 просрочено; homework делегирует). node --check
всех файлов + инлайна обоих HTML.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Добавлено такое же действие, как [Z] в control-panel: POST /api/admin/reset-system
(+ /reset-system/plan для предпросмотра), только admin. Общая логика вынесена в
src/services/systemReset.js (classify/pickKeptAdmin/runReset) — реюзится CLI и эндпоинтом.
Веб-эндпоинт безопаснее CLI: сохраняет ТЕКУЩЕГО админа (оператор остаётся залогинен),
делает бэкап БД ДО сброса (wal_checkpoint + копия в data/backups/), требует body.confirm='СБРОС'.
UI — «Опасная зона» в overview-секции: предпросмотр плана + ввод «СБРОС» + результат с именем бэкапа.
db.js: добавлен db._path (нужен бэкапу при сбросе). Логика проверена смоуком на копии живой БД
(16 юзеров удалено, контент сохранён, REASSIGN на админа, гейм-счётчики обнулены, 0 висячих FK).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Раньше ученик видел лишь 1 тест на предмет (дефолтный). Теперь учитель/админ
может пометить любой свой тест доступным, и он появляется в каталоге на дашборде.
- Миграция 079: tests.available_to_students (default 0).
- testController: list для ученика отдаёт тесты с available_to_students=1 и вопросами;
create/update принимают флаг; update сделан частичным (не затирает поля при toggle).
- admin «Тесты»: бейдж «Доступен ученикам» + быстрый тумблер «Ученикам/Скрыть»
(toggleTstAvail; конструктор доступен и учителям — видят свои тесты).
- Дашборд: виджет «Тесты» → секция «Доступные тесты» (loadAvailableTests), клик
запускает фикс-тест. Прячется, если доступных нет.
⚠️ Живой БД нужен npm run migrate (колонка).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Рассогласование: админ-настройка допускала режимы topic/random, но POST /api/sessions
принимает только exam/practice → клик по такому предмету падал с 400. Убрал topic/random
из валидатора subjects.js и из админ-дропдауна (SC_MODES). Дашборд: старые значения
topic/random коэрсятся в practice; предметы без вопросов в банке И без фикс-теста больше
не показываются (раньше давали 404 «No questions found» при запуске).
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>
Три проблемы 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>
Геймификация (gamification) была только в FS_FEATURES (free-student grid) — UI
позволял выключить её лишь для роли «свободный ученик», а ГЛОБАЛЬНОГО тумблера в
GAME_FEATURES не было, хотя бэкенд флаг feature_gamification_enabled и kill-switch
это поддерживают. Добавлен в GAME_FEATURES как «Геймификация (всё)» — теперь админ
может выключить XP/монеты/ачивки/магазин/стрики/лидерборд глобально из UI.
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>
Раздел «Доступ · контент» использовал браузерный confirm() («Подтвердите
действие на localhost…») для закрытия доступа у всех классов и копирования.
Заменены все 5 вызовов на LS.confirm (та же модалка, что в остальном админ-
разделе): «Закрыть доступ» (danger) и «Скопировать доступ» (danger:false).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
variants.js хардкодил «Вариант ${n}» в гриде-пикере, заголовке и подписи кнопки,
игнорируя поле label из listVariants (бэкенд его уже отдаёт через examVariantLabel).
Добавлен хелпер labelOf(n) → подставляет v.label с фолбэком. mock.js дропдаун уже
использовал label — там достаточно перезапуска сервера, чтобы бэкенд отдал метки.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Селект «Вариант» использовал .mk-input (узкий, под число) → подпись
«РТ-2024/25 · этап I» обрезалась. Задал width:auto/min-width:14rem/max-width:100%.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Вместо «Вариант 101/102/103» (технические номера) показываем источник:
«РТ-2024/25 · этап I/II/III». examVariantLabel() в exam-prep.js — единый
источник подписи: listVariants (пикер/dropdown) + variant_label в ответе
mock/:id (строка прохождения и результата). Номера в БД остаются 101+
(нужны для фильтра-диапазона [101;1999] и провенанса). math9 — fallback
«Вариант N» (не затронут). Новые варианты (104+) — дописывать в VARIANT_LABEL.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- exam-prep.js: MOCK_VARIANT_RANGE — для ctmath показываем как пробники
только чистые 30-задачные варианты [101;1999]; год-пачки (variant=год
2011-2024 и 0, до 114 задач) остаются пулом для тренажёра по темам,
но скрыты из пикера/mock-start/просмотра вариантов. math9 (1..80) не затронут
(диапазон только для ctmath).
- mock.js: пикер «По варианту» — выпадающий список реальных вариантов
(через listVariants) вместо number-input 1..N; раньше для ctmath он
предлагал 1..18 и не доходил до 101 → пробник по варианту не запускался.
- cleanup_ctmath_bank.js: идемпотентный скрипт — ретайр битого id=1419
(mc с противоречивым ответом → long), variants_count → 3 (чистых вариантов).
- seed_*: variants_count считается по диапазону [101;1999] (консистентно с роутом).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
В инструменте «∠ рёбер» общий рисовальщик _drawAngleArc всегда чертил дугу,
включая случай 90° — должен быть квадратный маркер прямого угла.
- _drawAngleArc: при |angle−90|<0.5° рисует угловой «квадратик» (p1=center+
n1·r, p3=center+(n1+n2)·r, p2=center+n2·r, r=radius·0.7) вместо дуги.
Подпись «∠ABC = 90.0°» и лучи угла рисуются отдельно в обработчике —
не затронуты. Для не-прямых углов поведение прежнее (дуга).
Верификация: node --check OK; headless-смоук 10/10 (90° → 3-точечный квадрат
с верной геометрией в любой плоскости; 89.6° в допуске → квадрат; 60/88/130°
→ дуга; полный поток _onEdgeAngleClick на угле куба → квадрат); эмодзи/eval/
new Function — 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Не было способа убрать само тело со сцены. Добавил тумблер «Фигура» в
начале секции «Отображение»: скрывает грани, рёбра, вершины и подписи тела,
оставляя сетку/оси и ВСЕ построения, точки, сечения и выделения — удобно
работать с конструкциями на «пустом» поле.
- StereoSim: флаг showFigure (деф. true) + toggleFigure(v) — переключает
_figGroup.visible/_labelGroup.visible (флаг переживает _clearGroup, поэтому
фигура остаётся скрытой и после перестроения при смене параметров). При
смене типа фигуры (setFigure) тело снова показывается.
- Панель: st-toggle-row #stg-figure; диспетчер stereoToggleSt('figure');
setStereoFigure возвращает тумблер в «вкл» для новой фигуры.
Верификация: node --check OK; headless-смоук 13/13 (деф. видна; скрытие
прячет fig+labels, но grid/construct/poly/point-группы остаются; перестроение
сохраняет скрытие; обратное включение; setFigure ре-показывает; dispose);
эмодзи/eval/new Function — 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
В инструменте «Соединить» подпись длины у каждого отрезка рисовалась всегда.
Добавил переключатель «Длины отрезков» (секция «Инструменты»): прячет только
подписи длин, сами отрезки и точки остаются.
- StereoSim: флаг showConnectionLengths (деф. true), гард в
_rebuildPointVisuals, метод toggleConnectionLengths(on). Предпочтение
переживает смену фигуры (не сбрасывается в setFigure).
- Панель: st-toggle-row #stg-connlen + glue stereoToggleConnLen.
Верификация: node --check OK; headless-смоук 8/8 (деф. вкл, подпись
гейтится флагом, линия/маркеры сохраняются, предпочтение переживает
setFigure); эмодзи/eval/new Function — 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Панель за фазы A–C разрослась до ~14 всегда-раскрытых секций (длинный
скролл, тяжело ориентироваться). Сделал её удобнее:
- _stereoInitPanel() (вызов из _openStereo, идемпотентно) оборачивает
контролы каждой секции в .st-acc-body; заголовки .gp-section-title →
кликабельные .st-acc-hdr с шевроном; состояние секций в localStorage.
- Тройку фигурных секций (Многогранники/Правильные/Тела вращения) слил в
одну «Фигуры» (под-метки .st-sublabel). По умолчанию открыты «Фигуры» и
«Параметры», остальное свёрнуто.
- Кнопки «Развернуть всё / Свернуть всё» (stereoAccAll), клавиатура
(Enter/Space на заголовке), role=button/tabindex.
- Только раскладка: ни один контрол/обработчик не изменён (узлы лишь
перемещены в тело секции). Затронуты stereo.js + lab.css.
Верификация: node --check OK; headless DOM-смоук (мини-DOM + реальный
stereo.js в vm) 22/22: 12 сворачиваемых секций, тройка фигур слита (2
под-метки внутри «Фигуры»), пары заголовок→тело, дефолт-открытие,
тоггл+персист, развернуть/свернуть всё, идемпотентная переинициализация,
ни одна строка контролов не потеряна. Эмодзи/eval/new Function — 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Фаза B раунда «Конструктор» (умные точки для построений).
B1 — деление отрезка m:n: задаёшь m,n, кликаешь 2 точки A,B → точка делит
AB как AM:MB = m:n (t=m/(m+n)), создаётся как точка-построение M,N,K…
B2 — точка по координатам: поля x/y/z + кнопка → addPointAt.
B3 — перетаскивание построенных точек мышью: drag в плоскости, обращённой
к камере (нормаль фиксируется на старте), приоритет над орбитой; снапшот
истории на старте → undo откатывает весь drag. Непараметрично: downstream-
объекты за перетаскиванием не следуют (параметрический граф — бэклог).
- StereoSim: setDivideMode/setDivideRatio (+ ветка в _onConstructClick),
addPointAt; setDragPointMode/_pickCPointAt/_beginCPointDrag/_rayPlaneHit/
_dragCPointWithRay/_dragCPointAt/_endCPointDrag; pointer-хендлеры
(down=начать drag, move=тащить, up=завершить); сброс в setFigure;
интеграция в _stereoDeactivateTools.
- Панель: блок «Точки» (кнопки Деление/Тащить, поля m:n, поля x,y,z +
«Точка (x,y,z)»); glue stereoDivideMode/DivideRatio/AddCoordPoint/
DragPointMode.
Верификация: node --check OK; headless-смоук 25/25 (деление 1:1/1:2/3:1,
координатная точка + отказ NaN, ray∩plane вкл. parallel/behind, drag begin→
move→end с проверкой позиции и снапшота истории + undo, взаимоисключение
режимов, setFigure-сброс, dispose); эмодзи/eval/new Function — 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Фаза C раунда «Конструктор» (C2 покрыта Фазой A, C4 отложена).
C1 — любую построенную плоскость можно показать сечением тела: клик по
плоскости в дереве (нормальный режим) → setSectionPlane: заливка
многоугольника + подписи вершин K,L,M… + площадь и периметр в readout-
панели. Удаление плоскости / очистка / смена фигуры сбрасывают сечение.
C3 — «Натуральная величина» сечения (getTrueShape): многоугольник сечения
разворачивается в свою плоскость (ортонормированный базис от нормали) с
сохранением истинных длин → 2D-SVG мини-панель со штриховкой (pattern),
подписями вершин, длинами сторон и S/P. Появляется автоматически при
активном сечении.
- StereoSim: _sectionPlaneId, setSectionPlane, _activeSectionPolygon,
_sectionVertexLabel, getTrueShape; _drawPlaneObject заливает+подписывает
активное сечение; getReadout добавляет S/P; getConstructions отдаёт
sectionId + per-plane section; pickConstructObject в нормальном режиме
тогглит сечение по плоскости.
- Панель: контейнер #construct-trueshape + подсказка; glue
_stereoUpdateTrueShape (SVG-рендер) вызывается из _stereoUpdateUI; строки
плоскостей в дереве всегда кликабельны, тег «(сечение)».
Верификация: node --check OK; headless-смоук 26/26 (квадрат y=2: S=16,P=16;
readout/дерево/тоггл; true-shape длины K,L,M,N=4, площадь=16; сохранение
длин и площади для прямого И наклонного сечения; 2D-shoelace=S; удаление/
очистка/setFigure сбрасывают сечение; dispose); эмодзи/eval/new Function — 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Фаза A раунда «Конструктор» (под ученика-самоучку). Прямая по 2 точкам
(имена a,b,c…) и плоскость по 3 точкам (имена α,β,γ…) как именованные
объекты сцены. Плоскость рисует полупрозрачный квад + пунктирную рамку +
сечение тела этой плоскостью (через _sliceByPlane) — сразу осмысленна.
- StereoSim: _lines/_planes (сериализуемые {x,y,z}), _constructGroup,
setLineMode/setPlaneMode, _onConstructClick, _createLine/_createPlane,
_rebuildConstructions/_drawLineObject/_drawPlaneObject, removeLast/clear,
getConstructions (с уравнением плоскости). Сброс в setFigure, очистка в
dispose, перерисовка подписей в toggleLabels, счётчик в info().
- Панель «Построения» в labs-bodies.html + glue (stereoLineMode/PlaneMode/
ConstructUndo/Clear, _stereoUpdateConstructList); интеграция в
_stereoDeactivateTools и _stereoUpdateUI.
- План: Фазы A и C в plans/STEREO_3D_IMPROVEMENT.md.
Верификация: node --check OK; headless-смоук 35/35 (создание/имена/нормаль/
коллинеарность/rebuild/summary/remove-last/clear/click-путь/setFigure-сброс/
dispose); эмодзи/eval/new Function — 0.
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>
Не было UI для управления exam_tracks.enabled (только флаг в БД, ставился
миграцией). Добавлена админ-секция «Экзамен-модули»:
- backend exam-prep.js: GET /admin/tracks (все треки, вкл. выключенные, + число
заданий) и PATCH /admin/track (exam_key, enabled), обе requireRole('admin').
Пути без :examKey, чтобы не задеть гейт content_access.
- frontend: секция sections/exams.js (список треков + переключатель enabled),
вкладка в admin.html (admin-only через ADMIN_ONLY_TABS, locked для не-админов),
регистрация в admin.js (ROUTE_TO_SECTION).
Выключенный трек скрыт у учеников и пропадает из каталога прав доступа (тот
берёт exam_tracks WHERE enabled=1). Доступ ученикам по-прежнему в «Доступ · контент».
Требует перезапуска бэкенда + Ctrl+F5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(quantik-game): фаза 5 — авторинг игровых уровней в sim-builder + раздача
Учитель собирает игровой уровень без кода: новая (аддитивная, сворачиваемая)
панель в sim-builder задаёт блок goal (when/title/hint/hold/fail) + до 3
звёзд + game-мету (chapter/order/par_ms); выражения проверяются inline через
SimExpr.compile (без eval). buildSpec/loadFromSim — round-trip без потерь
(goal/game пишутся только при включённом слое; обычная sim не меняется).
Кнопка «Играть» монтирует черновик в SimEngine-модалке (HUD цели из Ф0).
QuantikLevels стал async: подмешивает custom_sims cat=game (свои+
published) в реестр (custom:<dbid>), offline-safe, строки без goal
отбрасываются; deep-link /quantik?level=custom:<id> с серверной проверкой
доступа (own|published → иначе 403/404), мимо геймплейного гейта unlockStars.
Раздача классу — реюз share Ф6 (game-aware ссылка + durable pushNotif).
Правки sim-builder строго аддитивны (параллельная сессия). npm test 259/8
baseline; quantik-authoring 6/6; lint:routes 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@