Commit Graph

415 Commits

Author SHA1 Message Date
Maxim Dolgolyov 20b8ce2c5b feat(trainer): P1 — темы/навыки, +8 генераторов, подробные пошаговые решения
- таксономия тема→навык (topics/byTopic), метаданные topic/order/subject/grade
- 13 генераторов в 3 темах: Уравнения (+a(x+b)=c(x+d), (ax+b)/c=d), Пропорции (3), Проценты (3)
- проценты как compute-задачи: текстовый prompt + проверка подстановкой (latex уравнения скрыт)
- подробные объяснения: каждый шаг расписан словами + шаг «Проверка» (подстановка корня)
- UI: вкладки тем + чипы навыков, бейджи мастерства, авто-выбор первой неосвоенной темы/навыка
- движок: exprToLatex чинит отрицательные множители (7·(−5)), поле kind, нумерованные шаги решения
- смоуки 238/238 (движок) + 19/19 (страница); план: P1 отмечен DONE

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 13:29:44 +03:00
Maxim Dolgolyov c370eaa803 feat(trainer): ИИ-тренажёр — генераторы задач + SimExpr-верификатор, прогресс, фича-флаг
- движок _trainer_engine.js: instantiate/generateBatch/verifyRoot/checkStudentAnswer/exprToLatex
- 5 генераторов уравнений 7 класса (generators.js), приём «корень-вперёд» → целые ответы
- страница /trainer: KaTeX-рендер, чипы-темы, мгновенная проверка, подсказка/решение, авто-выбор навыка
- прогресс practice_progress (мигр.081) + /api/practice/progress|attempt + LS.practiceProgressList/Submit
- фича-флаг trainer: тумблер в админке (Модули), requireFeature, FEATURE_HREFS (скрытие сайдбара+редирект), MODULE_CATALOG
- fix: подключён Lucide CDN на странице (иначе иконки сайдбара пустые)
- тесты practice.test.js (10/10); план развития plans/ai-trainer/PLAN.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 13:11:47 +03:00
Maxim Dolgolyov e38abff02a feat(assistant): срок жизни диалога — 7 дней без общения
saveChat пишет метку времени, loadChat сбрасывает диалог, если с последней
реплики прошло больше CHAT_TTL (7 дней). Обратная совместимость со старым
форматом-массивом. Сноска обновлена.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 23:07:11 +03:00
Maxim Dolgolyov 7c0ccc7282 feat(assistant): диалог сохраняется между заходами (localStorage, per-user)
_chat теперь персистится в localStorage (ключ asst_chat_<uid>, последние 30
сообщений) при каждом ответе и восстанавливается при загрузке виджета. Живёт,
пока ученик сам не нажмёт «Очистить» (чистит и хранилище). Сноска обновлена.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 23:04:43 +03:00
Maxim Dolgolyov c045ea02b0 feat(assistant): больше рабочей памяти (~14 сообщений) + оформление сноски
История разговора расширена с 6 до 14 сообщений (фронт _chat.slice и бэкенд
история в ask/askStream). Сноска о памяти переоформлена: иконка-история + чище
текст, акцент на количестве.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 22:56:51 +03:00
Maxim Dolgolyov b310029e8d style(assistant): компактные режимы — иконка + короткая подпись
Кнопки режимов перерисованы как тулбар: вертикально иконка (inline SVG .ic) +
лаконичная подпись. Длинные ярлыки сокращены (Проверить решение -> Проверить,
Тест в банк -> В банк, Нарисовать -> Рисунок); полный смысл в title. Рендер
data-driven из MODE_DEFS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 22:54:19 +03:00
Maxim Dolgolyov 29fc270c0e fix(assistant): «Забыть всё» теперь сбрасывает и производный профиль
clearMemory ставит точку отсчёта asst_forget_<uid> (datetime now); слабые
предметы/темы в _studentProfile считаются только по активности после неё, так
что панель памяти видимо очищается. Кнопка «Забыть всё» в виджете показывается
лишь при наличии заметок/слабых тем, профиль помечен как авто-обновляемый.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 22:36:29 +03:00
Maxim Dolgolyov 7b4a274aed feat(assistant): умная память Квантика — свежесть, категории, темы по всем предметам
Память об ученике (1+2+3 из плана), всё строго на русском:
- СВЕЖЕСТЬ: эффективный вес заметок с затуханием по времени (полураспад ~31 день),
  в промпт идут только актуальные (порог по effWeight). Старое тихо тает.
- УМНОЕ СЛИЯНИЕ: вместо дедупа по первым 24 символам — стем-токены (русская
  морфология) + Jaccard; похожие заметки сливаются (вес+, текст освежается),
  а не плодят дубли. Лимит 18.
- КАТЕГОРИИ: экстрактор классифицирует факт (трудность/предпочтение/цель/
  сильная сторона/личное), возвращает JSON; запоминаются и сильные стороны/
  интересы, не только проблемы. Гард по кириллице — не-русский текст не попадает.
- ТРУДНЫЕ ТЕМЫ ПО ВСЕМ ПРЕДМЕТАМ: профиль считает слабые темы из user_answers+
  topics (любой предмет, русские названия), объединяя с экзаменом, а не только math9.
- UI «Что я о тебе помню»: у заметок русская плашка-категория.
Без миграции (колонки kind/weight/updated_at уже есть). Проверено: логика 8/8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 22:27:28 +03:00
Maxim Dolgolyov 4c9656e8a8 style(assistant): вау-уровень окна Квантика
- стекло-блюр фона окна (backdrop-filter blur+saturate), глубже тень;
- полноширинная градиентная шапка (фиолетово-бирюзовый) со скруглением;
- аватар с зелёным «онлайн»-пульсом;
- анимированный индикатор печати (три прыгающие точки) вместо текста Думаю;
- плавное появление каждого сообщения (slide-in).
Всё уважает prefers-reduced-motion. Только frontend/js/assistant.js, inline SVG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 22:17:55 +03:00
Maxim Dolgolyov b6c08f1b16 style(assistant): премиальный редизайн окна Квантика
Окно «Спроси Квантика» — удобнее, красивее, солиднее:
- шире (418px), многослойная мягкая тень с фиолетовым отливом, скругление 20px;
- настоящая шапка с аватаром в круге (градиентный фон) и разделителем;
- пузыри сообщений: ученик — фиолетовый градиент с хвостиком, ассистент —
  светлая карточка с тонкой рамкой; мягкая тень;
- поле ввода с фокус-кольцом + отдельная кнопка отправки (стрелка-самолётик),
  градиентная — удобнее на телефоне и нагляднее;
- режимы и чипы — мягкие пилюли, активный режим градиентный с тенью;
- область чата выше (54vh) с аккуратным фиолетовым скроллбаром.
Только frontend/js/assistant.js, без эмодзи (inline SVG).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 22:13:02 +03:00
Maxim Dolgolyov 1915026bff feat(assistant): авто-переиндексация системы при смене флагов + пометка «без описания»
- updateFeatures вызывает _autoReindexSystem(): при тоггле любого модуля снимок
  знаний о системе обновляется сам (только если уже индексировали — не создаёт
  KB на пустом месте). Кнопку жать больше не нужно после смены флагов.
- getAssistant отдаёт systemUndoc — модули с фича-флагом, но без записи в
  каталоге; админ-карточка показывает «Без описания: …» (пассивная подсказка,
  без пушей), чтобы при желании дополнить «Описание системы».

Проверено: авто-реиндекс (не создаёт пустой / обновляет существующий) + undoc 3/3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 21:34:43 +03:00
Maxim Dolgolyov 08da26afca feat(assistant): индексация системы из админки — Квантик знает актуальные модули
Кнопка «Сохранить и проиндексировать систему» в /admin#assistant собирает снимок:
- статус модулей по фича-флагам (что ВКЛЮЧЕНО/ВЫКЛЮЧЕНО сейчас) + каталог разделов;
- редактируемое «Описание системы» админа.
Снимок кладётся в app_settings.assistant_system_kb и подмешивается в ответы:
systemContext(q) ищет по знаниям (стем-префикс под русскую морфологию) и
добавляет в контекст — Квантик опирается на актуальное состояние и не предлагает
отключённое.

Бэкенд: MODULE_CATALOG + buildSystemKb + indexSystem (POST /admin/assistant/index-system),
saveAssistant(+systemDoc), getAssistant(+systemDoc/Count/At), systemContext в ask и askStream.
Клиент: LS.adminAssistantIndexSystem. Без миграции (хранение в app_settings).
Проверено: логика снимка/поиска 5/5, node --check всех файлов.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 21:27:53 +03:00
Maxim Dolgolyov 4be7f9a07c fix(lab-organic): жидкость в пробирке не вылезает за стекло
Клип жидкости был прямоугольник на всю ширину + полукруг — вместе они
шире трубки в зоне закруглённого дна, и заливка торчала за стеклом.
Заменён на путь по внутреннему контуру пробирки (прямые стенки +
дугообразное дно, тем же радиусом w/2-4, что и обводка стекла).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:45:02 +03:00
Maxim Dolgolyov 7829360391 fix(lab-organic): колбы больше + гарантированно в зоне видимости
Пробирки «Качественных реакций» масштабируются по высоте канваса
(190..340px вместо фикс. 150, шире), а вертикальная позиция ty
клампится (≤210) — даже при некорректно большой высоте канваса
колбы всегда остаются в верхней видимой части, а не уезжают за экран.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:38:54 +03:00
Maxim Dolgolyov 09dc62dc96 fix(lab-organic): колбы «Качественных реакций» уезжали за пределы экрана
Canvas был прямым flex-ребёнком (flex:1). _drawQual выставляет
c.height = rect.height*dpr → intrinsic-высота канваса росла, а при
min-height:auto flex-элемент не сжимался ниже неё → разгон высоты
(×dpr на каждую перерисовку). Пробирки центрировались по H/2 и
оказывались далеко ниже видимой области.

Канвас обёрнут в position:relative;flex:1;overflow:hidden и сам стал
position:absolute (как рабочий канвас конструктора молекул) —
intrinsic-размер больше не влияет на раскладку.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:34:58 +03:00
Maxim Dolgolyov 8303d483cc fix(labs): SVG-стрелки уравнений рисовались как сырой текст на canvas
Уравнения реакций содержат inline <svg class=ic> стрелки. На canvas
(fillText) разметка показывалась буквально. Добавлен общий хелпер
ChemVisuals.cleanIcons (SVG→Unicode →/↑/↓), применён в flask (eq),
redox (s.txt) и chemsandbox (ответ квиза — был единственный незакрытый
путь мимо _csClean).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:31:35 +03:00
Maxim Dolgolyov 59ea5e7d65 fix(lab-measure): оверлей измерений на весь экран (SVG не растягивался по inset)
#lm-svg — это <svg> (заменяемый элемент с intrinsic 300x150); inset:0 без явных
размеров его не растягивал, поэтому линейка/угол рисовались за пределами видимой
области и казались нерабочими (панель-div при этом видна). Добавлены width:100vw;
height:100vh — оверлей теперь покрывает вьюпорт, инструменты видны и перетаскиваются.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:26:31 +03:00
Maxim Dolgolyov 254f373522 fix(lab-measure): «Скрыть» (×) закрывает панель целиком, а не только оверлей
Кнопка off раньше прятала линейку/угол, но оставляла тулбар на экране —
теперь снимает и bar (полное закрытие, как ожидается от ×).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:17:22 +03:00
Maxim Dolgolyov cd0ce17a60 feat(lab-graph): удобство и красота — скрытие функций, точки, контролы вида, пинч
«График функции», большой апгрейд UX:
- у каждой функции кнопки «глаз» (скрыть/показать, не удаляя) и «очистить»;
  скрытая — приглушена и зачёркнута, исключается из графика/hover/значений
- плавающие контролы вида поверх canvas: зум +/−, сброс вида, тумблер «Особые точки»
- ОСОБЫЕ ТОЧКИ: нули функций, y-перехваты и пересечения кривых — ringed-точки
  с подписью координат (бисекция по смене знака; правка: точные нули на узлах
  сетки больше не теряются; дедуп; подписи скрываются при «частоколе» >22 точек)
- пинч-зум двумя пальцами к центру жеста (к 1-пальцевой панораме)

Движок: setHidden/setShowPoints/_drawPoints/_findZeros/_visible; hover и
инфобар уважают скрытие. Только фронт. node --check OK; zero-finder 5/5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:11:21 +03:00
Maxim Dolgolyov fa29332bcd feat(lab-graph): введённые функции — редактируемое KaTeX-поле
Введённая функция показывается отрисованной формулой KaTeX прямо в строке;
клик по формуле → правка текста на месте (raw input + живое превью под полем),
клик мимо/blur → снова формула. Реализовано без MathQuill: .fn-field держит
<input> и .fn-math (KaTeX), класс has-math переключает отображение по фокусу.

- renderFnMath() рисует формулу в строке; _fnDisplay() решает режим (фокус+значение)
- focus/blur/mousedown-обработчики в _initGraphPanel (идемпотентно)
- живое превью .fn-preview теперь видно ТОЛЬКО при правке (:focus-within), цвет функции
- graphInsert/applyPreset/state-apply/clearAll/default-fn0 обновляют math-поле
- _katexInto() — общий безопасный рендер

Только фронт. node --check OK; логика вставки 5/5 (прошлый прогон).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:02:08 +03:00
Maxim Dolgolyov 000e42f9b3 feat(lab-graph): KaTeX-формулы + панель ввода как редактор формул
«График функции»:
- примеры (чипы) и живой предпросмотр каждой функции рендерятся в KaTeX
  (data-tex на чипах, _initGraphPanel рендерит при открытии)
- предпросмотр теперь всегда виден, крупный и в цвет функции; пустое поле
  показывает плейсхолдер-формулу приглушённо
- НОВОЕ: keypad вставки структур (x², xⁿ, √, a/b, |x|, π, sin/cos/tg/ln/eˣ, ())
  — клик вставляет в активное поле по каретке (как редактор формул в PowerPoint)
- graphInsert(token) с маркером каретки |; активное поле отслеживается по focus

Только фронт. Проверено: node --check, логика вставки 5/5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 18:56:31 +03:00
Maxim Dolgolyov c49077abbc feat(assistant): живость питомца — лицо реагирует на диалог (фича 6/6)
Лицо Квантика в шапке чата (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>
2026-06-24 15:12:49 +03:00
Maxim Dolgolyov 78aea47619 feat(assistant): генерация тестов в банк вопросов (фича 5/6)
Учитель: режим «Тест в банк» в Квантике — тема/текст превращается ИИ в вопросы
с выбором ответа, ревью в чате (варианты, верный подсвечен, пояснение),
кнопка «Сохранить в банк» (выбор предмета + тема) создаёт их через 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>
2026-06-24 15:09:02 +03:00
Maxim Dolgolyov bc0ed1892f feat(assistant): авто-здоровье провайдеров + ручная проверка (фича 4/6)
Новый модуль 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>
2026-06-24 15:02:37 +03:00
Maxim Dolgolyov 40c3152fe8 feat(assistant): сократический / анти-чит режим (фича 3/6)
- тумблер учителя «Сократический режим» (/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>
2026-06-24 14:57:24 +03:00
Maxim Dolgolyov 2506a72806 feat(assistant): контекст урока/страницы для Квантика (фича 2/6)
Квантик теперь знает, где находится ученик:
- pageHint() — лёгкий ситуативный контекст («ученик на странице учебник:
  «Физика 7, §12»») подмешивается к ЛЮБОМУ свободному вопросу автоматически
- getPageContext() расширен с учебника на уроки (theory/course/lesson):
  «Объяснить этот урок / Конспект урока / Флешкарты из урока»
- метки чипов адаптируются (параграф/урок)

Бэкенд уже принимал context (pageCtx) — правок не потребовалось.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:52:42 +03:00
Maxim Dolgolyov 089f93b8ee feat(assistant): стриминг ответов Квантика (фича 1/6)
Ответ модели «печатается» вживую через 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>
2026-06-24 14:50:11 +03:00
Maxim Dolgolyov 5b4d9324a4 feat(assistant): поддержка keyless-шлюзов + пресет Pollinations
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>
2026-06-24 14:35:56 +03:00
Maxim Dolgolyov d15c15ef2a feat(assistant): сканер бесплатных моделей Kilo в админке
Кнопка «Сканировать модели» в /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>
2026-06-24 14:18:04 +03:00
Maxim Dolgolyov 4c1ce8394c feat(trigcircle): шкала значений по оси Y на графике (координатная плоскость)
- y-ось графика теперь подписана значениями (KaTeX):
  sin/cos — 1, ½, 0, −½, −1; tg/ctg — 3, 2, 1, 0, −1, −2, −3
- пунктирные линии уровней + подписи слева от панели
- подписи прячутся при смене функции (лишние уровни tg/ctg)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:44:29 +03:00
Maxim Dolgolyov 0640efc82c feat(trigcircle): развёртка угла на графике + KaTeX-подписи граф-вида
- развёртка: участок кривой [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>
2026-06-24 13:35:28 +03:00
Maxim Dolgolyov 7562d1a77b fix(trigcircle): координатная подпись больше не перекрывает угол
Координатный тултип (cos; sin) выносится радиально НАРУЖУ за точку (вдоль луча от центра),
а не просто со смещением — так KaTeX-плашка значений всегда дальше от центральной дуги угла
и её подписи (π/3 и т.п.), наложения нет.

Verified: node --check; смоук — coord-подпись дальше от центра, чем сама точка.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:08:09 +03:00
Maxim Dolgolyov 1707a510a9 feat(trigcircle): KaTeX-оверлей для подписей на canvas (координаты, значения, угол)
На <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>
2026-06-24 11:03:39 +03:00
Maxim Dolgolyov 48158ea88d feat(trigcircle): Фаза 5 — чётность/нечётность (−α) + периоды
Тумблер «Чётность (−α)»: на окружности рисуется зеркальная точка −α (отражение через
ось 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>
2026-06-24 10:51:55 +03:00
Maxim Dolgolyov fe6df8fb98 feat(trigcircle): Фаза 4 — таблица значений (особые углы, KaTeX)
Тумблер «Таблица значений» → компактная таблица первой четверти (0/30/45/60/90°)
с точными sin/cos/tg/ctg на KaTeX (строится один раз). Строка опорного острого угла
текущего положения подсвечивается (150° → подсвечена строка 30°). По симметрии/приведению
(Фаза 2) это покрывает все 16 углов.

Glue: _trigBuildValueTable (один раз) + trigToggleTable; подсветка строки в _trigUpdateUI
по refDeg. Панель: тумблер + #trig-table.

Verified: node --check; headless-смоук 10/10 (5 строк, заголовки, значения 30/45/90,
tg «не опр.», toggle показ/скрытие). Эмодзи нет.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:49:33 +03:00
Maxim Dolgolyov 244df71aec feat(trigcircle): вся математика панели на KaTeX (значения + угол)
Значения 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>
2026-06-24 10:47:30 +03:00
Maxim Dolgolyov dfa0535b63 feat(trigcircle): Фаза 6 — простейшие тригонометрические уравнения
Режим уравнения 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>
2026-06-24 10:41:07 +03:00
Maxim Dolgolyov cefb5e0836 feat(trigcircle): рендер формул через KaTeX
Блок «Точные значения · приведение» теперь рендерится 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>
2026-06-24 10:35:25 +03:00
Maxim Dolgolyov 5eed248702 feat(trigcircle): Фаза 2 — точные значения + формулы приведения
При исследовании выяснилось: Пифагор (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>
2026-06-24 10:30:02 +03:00
Maxim Dolgolyov d395e1083b feat(trigcircle): Фаза 1 — работа с углами + обзор (тренажёр тригонометрии)
План тренажёра в 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>
2026-06-24 10:23:31 +03:00
Maxim Dolgolyov 43df41287f feat(errors): сбор клиентских (браузерных) ошибок в админ-вкладку «Ошибки»
Глобальный репортер в 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>
2026-06-23 23:17:04 +03:00
Maxim Dolgolyov 3c45c606bf feat(admin/tests): пикер вопросов — серверный поиск по всему банку + «Показать ещё» + фильтры
Раньше «Добавить вопросы» в конструкторе тестов грузил лишь первые 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>
2026-06-23 22:17:17 +03:00
Maxim Dolgolyov 4b5be8442b fix(admin): «Открыть» зависшей сессии ведёт на её детали, а не в пустой список
Алерт «Зависла» (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>
2026-06-23 20:02:53 +03:00
Maxim Dolgolyov be9fdfa703 feat(wishes): трекер пожеланий по улучшению системы
Любой авторизованный пользователь подаёт пожелание (заголовок, категория, описание);
видит только свои. Админ видит все, фильтрует по статусу, ведёт по статусам
(новое → запланировано → в работе → готово / отклонено) и пишет ответ автору. Автор
получает уведомление при смене статуса (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>
2026-06-23 16:12:10 +03:00
Maxim Dolgolyov 0d4c658d93 refactor(assignments): единый модуль assignment-utils.js (тип/«сдано»/срочность)
Логика классификации типа задания и статуса «сдано» дублировалась в трёх местах
(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>
2026-06-23 14:09:34 +03:00
Maxim Dolgolyov 22c7b38e9a feat(admin): сброс системы «чистый запуск» в веб-панели
Добавлено такое же действие, как [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>
2026-06-23 11:45:13 +03:00
Maxim Dolgolyov c6d323ec6d feat(tests): витрина доступных тестов ученику + флаг «доступен ученикам»
Раньше ученик видел лишь 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>
2026-06-23 11:03:42 +03:00
Maxim Dolgolyov c5d440a7a9 fix(tests): режимы доступных тестов только exam/practice + скрытие пустых предметов
Рассогласование: админ-настройка допускала режимы 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>
2026-06-23 10:53:43 +03:00
Maxim Dolgolyov 38f8be9389 feat(features): тумблер «Путеводитель» (/sitemap)
Пункт «Путеводитель» теперь отключается как остальные модули: ключ 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>
2026-06-22 17:51:36 +03:00
Maxim Dolgolyov 83f0ba9c04 fix(features): пустой блок флешкарт, лаба в сайдбаре, мигание (FOUC)
Три проблемы 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>
2026-06-22 17:41:11 +03:00