Commit Graph

99 Commits

Author SHA1 Message Date
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 2d7833cad9 test: зелёный сьют — синхрон политики пароля (8), jsdom devDep, serial-прогон
Чинит 8 «baseline»-падений (теперь 330/330):
- auth (3): контроллер/фронт требуют пароль >=8, а схема роута (minLen:6) и тест
  (7-симв. 'pass123') устарели → схема register/profile 6→8, тест-пароли → 8 симв.
  (login/duplicate падали как следствие незарегистрированного юзера).
- page (5): jsdom не был установлен → добавлен в devDependencies.
- флакость jsdom-страниц при параллельном прогоне (фикс. wait под нагрузкой CPU) →
  npm test с --test-concurrency=1 (детерминированно; в изоляции тесты и так проходят).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:53:04 +03:00
Maxim Dolgolyov c780b6fd96 @
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>
@
2026-06-14 16:09:10 +03:00
Maxim Dolgolyov 978448d99b @
feat(quantik-game): фаза 3 — граф-уровни (движение по f(x)) + зоны

Новый тип уровня: Квантик едет по кривой y=f(x), которую игрок собирает
слайдерами коэффициентов, проходя сквозь зоны-препятствия. Движок
(аддитивно): plot.runner → env-поля curve.runX/runY/runDone (f компилится
1 раз, питает И кривую, И бегунок-героя, без само-ссылки); type zone
(forbidden/target/collect) → булево env-поле zone.hit. Грамматика
выражений ЗАКРЫТА — никаких inzone()-предикатов, только именованные
env-поля (модель t/tries из Ф0), без eval. Глава-созвездие functions из
5 уровней (луч/синус/парабола/модуль/экспонента), разблокировка 9/11/13/
15/17 (цепочка проходима). validateSpec принимает zone+runner. Все 5
уровней независимо проверены на движке (2★ достижимы). npm test 253/8
baseline; custom-sims 26/26; lint:routes 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-06-13 17:07:33 +03:00
Maxim Dolgolyov 351251d652 @
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>
@
2026-06-13 15:31:25 +03:00
Maxim Dolgolyov 9bd40c5d1c feat(flashcards): общие колоды — учитель назначает колоду классу/ученику
Учитель делится своей колодой с классом или конкретными учениками; карты общие
(одна копия), а прогресс у каждого свой — flashcard_reviews уже keyed по
user_id+card_id, поэтому ученик копит собственные интервалы на тех же картах.

- миграция 075: flashcard_deck_access (deck_id, type class|user, target_id) —
  зеркало folder_access; индексы по target и deck.
- deckAccess(): владелец/админ (canEdit) либо назначенный напрямую/через класс
  (canRead). listDecks отдаёт свои + назначенные (shared/can_edit/owner_name);
  getCards/getStudySession/submitReview пускают по canRead (ученик учится и
  ставит отзыв), правка карт/колоды — только владелец.
- share API (owner + роль teacher/admin): GET /shares, POST /share, DELETE
  /share?type=&target_id=; цель валидируется (свой класс / свой ученик).
- фронт: общие колоды с бейджем учителя, открываются read-only (CSS .readonly
  прячет ручки/удаление/правку, drag и inline-edit выключены), кнопка
  «Поделиться» с модалкой (вкладки Классы/Ученики, тоггл = add/remove share).
- тест flashcards-share 13/13 (шаринг класс/ученик, видимость, изучение+отзыв,
  правка 404, доступ 404, роль-гейт 403, чужой класс 403, снятие доступа).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 13:30:53 +03:00
Maxim Dolgolyov 5c01a5c7ed feat(flashcards): learning-steps SR — повторный показ «Снова» в сессии, лимит новых карт/день
Tier-1 апгрейд интервального повторения:
- schedule() с состояниями learning/relearning/review вместо плоского sm2():
  новая карта проходит шаги [1,10] мин, «Снова» возвращает на шаг 0 (минуты),
  «Знаю» продвигает шаг → выпуск (1д), «Легко» выпускает сразу (4д); зрелая
  «Снова» = lapse → relearning (ef−0.2, ×0.5).
- study-сессия: динамическая очередь — недоученная карта (graduated=false)
  возвращается через 3 карты и показывается снова в той же сессии.
- лимит новых карт/день (decks.new_per_day, деф.20) в getStudySession и бейдже.
- превью кнопок fcPreview() показывает минуты/дни, зеркало серверной логики.
- миграция 074: state/learning_step/lapses/created_at + new_per_day + индексы.
- тесты SRS 9/9 (шаги, lapse, лимит новых).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 13:10:00 +03:00
Maxim Dolgolyov cbb6edf372 feat(sim-builder): фаза 6 — раздача классу, клон, шаблоны, привязка к программе (custom_sims) 2026-06-13 13:06:30 +03:00
Maxim Dolgolyov 014c96db1e feat(sim-builder): фаза 3 — БД custom_sims + CRUD API с валидацией спеки и проверкой владения 2026-06-13 12:10:02 +03:00
Maxim Dolgolyov d05bb386a7 test(exam): Phase 6 — тесты exam-textbook-links.test.js (9/9 pass)
Проверяет: колонки в схеме, >= 90% задач размечены, topic_ref.paragraph
числовой тип, slug-значения из известных префиксов, fallback в exam_topics.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 16:18:57 +03:00
Maxim Dolgolyov bdc8bef857 feat(permissions): C-4a — API конструктора ролей (/api/roles, admin)
rolesController + routes/roles (admin, inline guards): GET список (с числом
пользователей), POST создать кастомную роль (имя-идентификатор + метка + base_roles;
засев прав из функциональной базы), PUT изменить, DELETE удалить (пользователей
возвращает на базу), GET /:name/permissions (эффективная карта база+оверлей + defs).
setPermission теперь принимает кастомные роли (ключ валидируется по базе, хранится
под именем роли). Смонтировано в server.js + тест-харнесс. Тест roles-api 5/5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:21:44 +03:00
Maxim Dolgolyov 32c2c44b76 feat(permissions): C-3 — пер-ролевые права кастомных ролей (резолвер + конфиг)
Миграция 056: снят CHECK с role_permissions.role (пересборка) → можно хранить
набор прав произвольной кастомной роли. isEnabled(uid,permRole,baseRole,key):
user override → role_permissions[customRole] → фолбэк role_permissions[base] →
дефолт реестра(base). requirePermission передаёт permRole=customRole||role.
getMyPermissions/getUserPermissions: roleMap = база + наложение кастомной роли.
Тест C-3: права кастомной роли перекрывают базу, фолбэк на базу. custom-roles 8/8,
permissions 17/17, backend без регрессий.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:11:56 +03:00
Maxim Dolgolyov 7cdb2e2af2 feat(permissions): C-2 — присвоение кастомной роли пользователю (users.custom_role)
Миграция 055: ADD COLUMN users.custom_role (безопасно, без пересборки users).
Модель: users.role = функциональная база (встроенная, CHECK ок, драйвит ветки
контроллеров и резолв прав), users.custom_role = имя кастомной роли. updateRole
(PATCH /api/admin/users/:id/role) принимает кастомные роли → ставит base_roles[0]
как базу + custom_role=имя; встроенная → custom_role=NULL; неизвестная → 400.
authMiddleware/optionalAuth читают custom_role → req.user.customRole; requireRole
расширяет до effectiveRoles(customRole||role). Тесты custom-roles 7/7; backend без регрессий.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:03:41 +03:00
Maxim Dolgolyov 5aa2dd1a4b feat(permissions): C-1 — фундамент кастомных ролей (roles table + наследование гейтов)
Phase C, Stage C-1 (ветка feature/custom-roles): таблица roles (name, label,
base_roles JSON, is_builtin) + засев встроенных. auth.effectiveRoles(role) —
кастомная роль наследует base_roles (какие встроенные гейты проходит); встроенные
— быстрый путь без БД. requireRole() теперь проверяет пересечение allowed с
effectiveRoles → 111 существующих гейтов не задеты (встроенные ведут себя как
прежде). Дизайн: PHASE_C_DESIGN.md. Тест effectiveRoles 5/5; полный backend pass.

ВАЖНО (обнаружено): users.role в канон-схеме имеет CHECK (admin/teacher/student/
free_student), безопасно пересобрать users (FK от многих таблиц, миграции в txn)
нельзя → присвоение кастомной роли пользователю пойдёт через users.custom_role (C-2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:57:10 +03:00
Maxim Dolgolyov a250d15f9a feat(permissions): B8 — временные права (expires_at) с авто-снятием
Миграция 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>
2026-06-03 14:43:06 +03:00
Maxim Dolgolyov 8b495f1508 feat(permissions): B7 — пресеты-профили прав (применение к классу одним кликом)
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>
2026-06-03 14:33:25 +03:00
Maxim Dolgolyov b95b639e75 feat(permissions): B6 — массовая выдача права классу (личный оверрайд всем ученикам)
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>
2026-06-03 14:27:58 +03:00
Maxim Dolgolyov 0a24a66a2e feat(permissions): B5 — группы прав (секции в UI + вкл/выкл всей группы)
registry: карта GROUP (Вопросы / Класс и ученики / Библиотека / Курсы и шаблоны /
Геймификация / Контент / Тесты и активность / Профиль), проброшена в byRole.group.
permissions.js: вкладка «Доступ · роли» рендерит права секциями по группам, у
каждой — «включить все / выключить все» (с подтверждением, если в группе есть
requireConfirmOff). Карточка вынесена в permCard(). Тест: definitions содержат group.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:21:52 +03:00
Maxim Dolgolyov 6bd1532735 feat(permissions): A4 — убрать role-level token_version bump (нет массового разлогина)
requirePermission читает права из БД на каждый запрос → серверное применение
живое. Прежний bump token_version при role-level изменении разлогинивал ВСЕХ
пользователей роли из-за одного тумблера. Убрали его: изменение применяется
сразу на сервере, клиент подхватит при следующем /permissions/me. User-level
bump оставлен (точечно одному пользователю — целевое обновление, не массовое).
Тест 3 обновлён: role-level НЕ бампает token_version + значение сохраняется.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:17:32 +03:00
Maxim Dolgolyov 7d474b40c0 feat(permissions): A3 — история изменений прав (endpoint + UI)
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>
2026-06-03 14:14:56 +03:00
Maxim Dolgolyov b0e385b2c6 feat(permissions): A2 — гигиена реестра (lint-тест) + ясные метки
Тест permissions-registry: каждый ключ из requirePermission/perm('…') в backend
есть в registry (ловит опечатки/дрейф; perm() падал на старте, сырой
requirePermission — нет). Заодно логирует ключи реестра, не используемые в
requirePermission (информативно — часть гейтится на клиенте через /me).
Метки theory.access/simulations.access переформулированы: «… доступен роли»
(видимость конкретного контента — по классам в «Доступ · контент»).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:11:52 +03:00
Maxim Dolgolyov 9ac2a612e0 feat(permissions): A1 — зависимости между правами (requires) + план переработки
registry: поле requires (questions.delete→manage, templates.public→manage,
courses.interactive→manage, simulations.quiz→access), проброшено в byRole.
auth.requirePermission: вынесен isEnabled(); право = own AND все requires
(дочернее не работает без родителя). /me и /users/🆔 effective с учётом
requires + requires в ответе. UI permissions.js: каскад — дочернее с
невыполненной зависимостью неактивно (тумблер заблокирован + «Требует: …»).
Тест зависимости. План: plans/permissions-rework/PLAN.md. Backend 216 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:10:20 +03:00
Maxim Dolgolyov b702b04ed2 feat(access): Фаза 2c — история правил + пресет «копировать доступ из класса»
История: 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>
2026-06-03 13:55:02 +03:00
Maxim Dolgolyov 9b7585ac7b feat(access): Фаза 1c — видимость курсов по классам (Фаза 1 завершена)
Миграция 052: мост «открыть все опубликованные курсы всем существующим классам»
(тип 'course' уже в CHECK из 051). courseController.list/search фильтруют курсы
для НЕпривилегированных по allowedRefs(uid,'course') (content_ref = courses.id как
TEXT); admin/teacher — все. /api/access/catalog отдаёт курсы; CONTENT_TYPES в
админ-UI = textbook,exam,sim,course → курсы управляются во всех режимах «Доступ».
Тест course-access 4/4 (allowlist+класс+privileged+каталог). Полный набор: 213 pass.

ВАЖНО: новый опубликованный курс по умолчанию закрыт (allowlist) — открыть классам
в админке. Мост сохранил видимость текущих опубликованных курсов у существующих
классов. class_courses остаётся для назначений с дедлайном (сверх видимости).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 13:31:10 +03:00
Maxim Dolgolyov 4549b4e819 feat(access): Фаза 1b — управление доступом к симуляциям в админке
Бэкенд /api/access обобщён на тип 'sim': catalog отдаёт симуляции (lab_sims),
summary/matrix/class — карты по всем типам. Админ-секция «Доступ» теперь
показывает «Симуляции» во всех трёх режимах (по контенту / по классу / матрица)
+ поиск; helpers (bucket/keyName/itemsOf) обобщены через карты типов
(CONTENT_TYPES=textbook,exam,sim; course зарезервирован). Теперь админ/учитель
могут открывать/закрывать конкретные симуляции классам и ученикам — закрыт UX-
разрыв из 1a (новые классы без UI-управления). Тест: каталог включает sims; 210 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 13:24:08 +03:00
Maxim Dolgolyov 9a145e5d62 feat(access): Фаза 1a — видимость симуляций по классам (добавочная модель)
Миграция 051: расширяет content_access.content_type на 'course'/'sim' (пересборка
таблицы — SQLite не умеет ALTER CHECK) + мост «открыть все включённые симуляции
всем существующим классам» → текущее поведение не меняется. GET /api/lab/sims
теперь фильтрует список для НЕпривилегированных по allowedRefs(uid,'sim'); admin/
teacher видят все. Ролевой simulations.access остаётся «модуль вкл.» (добавочно).
Тесты: lab-access (4/4, allowlist+класс+личное), lab-sims переведён на admin для
проверки полного каталога (видимость ученика — в lab-access). /api/lab в харнессе.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 13:19:29 +03:00
Maxim Dolgolyov 67a70c672d feat(access): Фаза 2a — режим «Матрица» класс × контент в админке
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>
2026-06-03 12:43:00 +03:00
Maxim Dolgolyov 1bbddc00c8 feat(access): Фаза 0 — целостность правил доступа + подтверждение массового закрытия
- contentAccess.purgeAccessFor(scope,id) — единая точка очистки content_access
  (нет FK). deleteClass и _deleteUserTx переведены на неё (убрано дублирование).
- Админ-UI: confirm() перед «Закрыть у всех / Закрыть весь» (необратимая массовая
  операция больше не срабатывает мгновенно).
- Новый тест content-access.test.js (9/9): allowlist, ученик>класс, наследование
  главой хаба, admin/teacher bypass, allowedRefs/filterTextbooks, purgeAccessFor,
  чистка правил при DELETE класса. Полный backend-набор: 203/206 (3 — baseline Auth).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 12:39:08 +03:00
Maxim Dolgolyov 5a2a1be089 feat(math5): Глава 3 «Обыкновенные дроби» — §1–18 + финал (Sonnet по эталону)
Дроби и доли, основное свойство и сокращение, смешанные числа, сравнение,
сложение/вычитание/умножение/деление дробей, задачи на дроби; геометрия:
параллельные/перпендикулярные прямые, периметр многоугольника, площадь и
площадь треугольника, среднее арифметическое, столбчатые диаграммы,
параллелепипед и объём (2D-изометрия). Inline-SVG визуалы (полоса долей,
сетка умножения, изометрия). Реализовано Sonnet-агентом инкрементально по
образцу math_5_ch1; проверено: грузится без ошибок, §1–18 без заглушек.

Учебник «Математика 5» наполнен ЦЕЛИКОМ (3 главы, 44 §). Тесты math5: 12/12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:34:15 +03:00
Maxim Dolgolyov 06e9846cc3 feat(math5): Глава 2 «Выражения. Уравнения» — §1–9 + финал (Sonnet по эталону)
Числовые выражения и порядок действий, выражения с переменными, уравнение
(SVG-весы + решение/проверка корня), формулы (P,S,путь), решение задач
уравнением, угол (SVG-рисунок + классификация острый/прямой/тупой/развёрнутый),
прикладные/занимательные/исторические § + финал-боссы. Реализовано Sonnet-агентом
по образцу math_5_ch1, проверено: грузится без ошибок, §1–9 без заглушек. Тесты: 11/11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:18:29 +03:00
Maxim Dolgolyov 12a08e7d42 feat(math5): Глава 1 ЗАВЕРШЕНА — §15–§17 (вокруг нас, движение, история чисел)
§15 Математика вокруг нас (задачи из жизни + прикидка в уме). §16 Движение/
взвешивание/переливание (s=v·t тренажёр + логические задачи). §17 Исторические
сведения (системы счисления; тренажёр римских цифр + квиз по истории чисел).
Глава 1 целиком: §1–17 + финал, все § наполнены (тест «нет заглушек»). Эталон
для Sonnet по Гл.2–3. Тесты math5: 9/9.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:41:17 +03:00
Maxim Dolgolyov 6e64339e8a feat(math5): Глава 1 §13–§14 — признаки делимости, простые/составные числа
§13 Признаки делимости (на 2/3/4/5/9/10; живой чекер: вводишь число → флажки
с объяснениями + квиз «делится ли нацело»). §14 Простые/составные (определения,
разложение на множители; интерактивное решето Эратосфена «найди простые 2..30»
+ квиз «простое или составное»). Шпаргалки/типсы §13–14. Тесты math5: 8/8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:38:10 +03:00
Maxim Dolgolyov a4a0ae1a77 feat(math5): Глава 1 §10–§12 — степень, деление с остатком, НОД и НОК
§10 Степень (a^n, основание/показатель; квадрат из клеток a×a + тренажёр степеней).
§11 Деление с остатком (a=bq+r; точки по b в ряд, остаток красным + тренажёр
неполного частного). §12 Делители/кратные, НОД/НОК (делители-чипсы с подсветкой
общих → НОД + тренажёр НОК). Шпаргалки/типсы §10–12. Тесты math5: 8/8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:36:02 +03:00
Maxim Dolgolyov 9ed89ab0c8 feat(math5): Глава 1 §7–§9 — округление, сложение/вычитание, умножение/деление
§7 Округление (правило + округление на координатном луче до десятков + до
сотен/тысяч). §8 Сложение/вычитание (столбик, свойства + тренажёр + «найди
неизвестное» как подготовка к уравнениям). §9 Умножение/деление (прямоугольник
из точек a×b как визуал + тренажёр ×/÷). Шпаргалки/типсы §7–9. Тесты math5: 8/8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:33:03 +03:00
Maxim Dolgolyov 5eb9fe3f1c feat(math5): Глава 1 §3–§6 — сравнение, фигуры, измерение, координатный луч
§3 Сравнение (правила + тренажёр знаков + «выбери наибольшее»).
§4 Точка/прямая/луч/отрезок/плоскость (SVG-галерея фигур + квиз «что изображено»
+ счёт отрезков по точкам). §5 Измерение отрезков (SVG-линейка с цветным отрезком
+ перевод единиц длины). §6 Координатный луч (Math6.numberLine ray: назови
координату + расстояние между точками). Шпаргалки/типсы §3–6. Тесты math5: 8/8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:29:59 +03:00
Maxim Dolgolyov bcc6c7e79b feat(math5): Глава 1 — §1 «Как решать задачу», §2 «Чтение и запись. Разряды», финал
§1: 4 шага решения (Пойа) + тренажёр «на каком шаге ученик» + решатель задач.
§2: натуральные числа и нуль, классы/разряды, интерактивная разрядная таблица
(ввод числа → раскладка по классам единицы/тысячи/миллионы) + тренажёр «цифра
в разряде». Финал главы 1 — 5 боссов (разряды/округление/действия/степень).
Шпаргалки/типсы/глоссарий для §1/§2/финала. §3–17 пока заглушки движка.
Тесты math5: 8/8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:18:41 +03:00
Maxim Dolgolyov c020a2c948 feat(math5): Phase 0 — фундамент учебника «Математика 5»
План (PLAN_MATH_5 + VISUAL: карта 22 визуал-компонентов), миграция
050_math5_hub (хаб math-5 + 3 главы: Натуральные числа §1–17, Выражения.
Уравнения §1–9, Обыкновенные дроби §1–18), страница-хаб (3 карточки +
курсовой финал из 3 боссов + звание «Математик 5 класса») и 3 каркаса глав
на ОБЩЕМ движке math6 (window.M6 с slug math-5-chN, ключи math5_*).
Baseline-тест math5-page: 6/6. § без билдера → заглушка движка.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:09:42 +03:00
Maxim Dolgolyov 21c18ce477 feat(math6): полировка Гл.6 §3 — перетаскиваемый треугольник
Math6Anim.triangleDrag (SVG): тащишь вершины A/B/C — тип пересчитывается
вживую по сторонам и по углам, штрихи равных сторон + метка прямого угла.
Блок «Песочница» перед интерактивами §3. Тесты math6: 20/20.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:13:01 +03:00
Maxim Dolgolyov 51db000545 feat(math6): полировка Гл.2 — pieGrow, balanceScale, constAreaRect
Math6Anim.pieGrow (растущие сектора, §7 — заменил статичный Math6.pie,
цвета синхронны легенде), balanceScale (весы a·d ? b·c, §3, кнопка «другой
пример»), constAreaRect (обратная проп. = постоянная площадь, §4, ползунок x).
Headless-safe. Тесты math6: 20/20 (поправлен ассерт §7 svg→canvas).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:07:56 +03:00
Maxim Dolgolyov 302b062649 feat(math6): полоса процента (Гл.2 §1) + фильтр множества (Гл.3 §1)
Math6Anim.barModel — полоса 0..100%, заполняется (easing) к проценту,
синхронно %↔десятичная↔дробь; вшита в §2.1 на тот же ползунок, что и сетка 100.
Math6Anim.setFilter — числа 1..12 по очереди проходят сквозь «фильтр свойства»
(чётные/кратные 3/больше 6), подходящие падают в множество; кнопки смены свойства;
вшита в §3.1. Теперь во ВСЕХ 6 главах есть canvas-анимации + stepPlayer везде.
Headless-safe. Тесты math6: 20/20.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:00:57 +03:00
Maxim Dolgolyov 97966ba2df feat(math6): симметрия (Гл.6 §4 центральная, §5 осевая) — reflectFold
Math6Anim.reflectFold: на координатной плоскости треугольник плавно
переходит на свой образ — центральная (поворот 180° вокруг O, режим
'central') или осевая (отражение через Oy, режим 'axial'); образ показан
красным пунктиром, ось/центр выделены. Один компонент закрыл §4 и §5.
Headless-safe. Тесты math6: 20/20.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:56:57 +03:00
Maxim Dolgolyov 555f701b57 feat(math6): умножение-прыжки (Гл.4 §7) + координатный тир (Гл.5 §1)
Math6Anim.numberLineJumps — a·b как a прыжков-дуг по b на числовой прямой
(зелёные вправо, красные влево, приземление на произведение); ползунки a,b.
Math6Anim.coordGame — «поставь точку (x;y)»: клик по узлу сетки, проверка,
счёт, при промахе показывает верную точку. План: 3D-тела исключены.
Headless-safe. Тесты math6: 20/20.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:53:47 +03:00
Maxim Dolgolyov f4ece6f5b1 feat(math6): термометр (Гл.4 §1) — ±числа и модуль наглядно
Math6Anim.thermometer: вертикальный термометр на canvas, ртуть плавно
поднимается/опускается к значению (easing), выше нуля — красный, ниже — синий;
подпись поясняет знак и |x| как расстояние до нуля. Ползунок −10..10.
Вшит в Гл.4 §1. Headless-safe. Тесты math6: 20/20.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:47:22 +03:00
Maxim Dolgolyov 8edab2196f feat(math6): stepPlayer — все «Разборы по шагам» стали интерактивными
Math6Anim.stepPlayer (DOM): пошаговый плеер с кнопками Назад/Дальше/Авто
и точками прогресса, рендерит KaTeX по шагам. Math6Anim.stepifyExamples
сканирует секцию и превращает карточки «Разбор по шагам» (<ol> в теле) в
такой плеер. Движок зовёт stepifyExamples в goTo (guarded) → автоматически
во ВСЕХ главах и параграфах, включая простые работы с дробями/столбиком.
Подключён math6_anim в Гл.2,3 (теперь во всех 6). Тесты math6: 20/20.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:44:34 +03:00
Maxim Dolgolyov 1fc1672acd feat(math6): живой график y=kx / y=k/x (Гл.5 §3) — плавное перетекание при k
Math6Anim.plotLive: canvas-плоскость с сеткой/осями; кривая плавно «перетекает»
(easing к целевому k). Переключатель прямая (y=kx, через начало) / обратная
(y=k/x, две ветви). Слайдер k (−4..4, шаг 0,5) двигает кривую вживую.
Вшито в Гл.5 §3 рядом со статичным графиком. Headless-safe. Тесты 19/19.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:33:47 +03:00
Maxim Dolgolyov 61de12e2de feat(math6): ещё 2 canvas-демо — прыжки по прямой (±) и машинка+график
Math6Anim расширен: numberLineWalk (анимированные стрелки-шаги a→b на
числовой прямой для сложения рациональных) и carGraph (машина едет по
дороге, а график «путь–время» вычерчивается синхронно; горизонталь = стоянка).
Вшито: Гл.4 §4 (прыжки, ползунки a,b) и Гл.5 §2 (машинка+график).
Headless-safe. Тесты math6: 19/19 (анимации в Гл.1/4/5/6 монтируются).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:29:03 +03:00
Maxim Dolgolyov 6b734957e9 feat(math6): canvas-анимации — движок math6_anim.js + 3 флагмана
Новый headless-safe движок window.Math6Anim (по канве chem7_anim:
RAF-цикл с паузой вне экрана через IntersectionObserver, prefers-reduced-motion,
в jsdom/HeadlessChrome getContext НЕ вызывается → тесты не падают).
Демо: rollingCircle (колесо катится → путь = C=2πr=πd), sweepArea
(радиус заметает круг → S=πr²), areaModel (площадная модель умножения a·b
на сетке 0,1). Вшито: Гл.6 §2 (колесо + заметание), Гл.1 §6 (умножение).
Тесты math6: 19/19 (+canvas-демо монтируются headless-safe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:24:37 +03:00
Maxim Dolgolyov fe378371bd fix(math6): запускать init() после экспортов хелперов в window
Реальная причина пустых §1 (заглушки) во всех главах: в math6_engine.js
вызов init() стоял ВЫШЕ строк window.makeCard=…/secNav=…. При обычной
загрузке через defer скрипт исполняется при readyState='interactive',
поэтому ветка `else init()` срабатывала синхронно — init→goTo→buildP1()
звал makeCard ДО его экспорта → ReferenceError 'makeCard is not defined'
→ перехват в ensureBuilt → заглушка. В jsdom-тестах баг не воспроизводился
(там старт шёл через DOMContentLoaded, экспорты успевали).

- init() теперь вызывается СТРОГО после всех window.* экспортов.
- ensureBuilt перечитывает window.M6 (надёжнее против устаревшего замыкания).
- html учебника всегда no-store (убрал кэш-причину стале-страниц).
- регресс-тест: init() обязан идти после window.makeCard. Тесты 18/18.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:58:05 +03:00
Maxim Dolgolyov 0bb48d3f04 feat(math6): курсовой финал на хабе + звание «Математик 6 класса»
Капстоун-бой из 6 испытаний (по одному из каждой главы: десятичные,
проценты, множества, рациональные, координаты, геометрия) с HP-баром.
Победа 5/6 → +150 XP (LS.xp) + звание «Математик 6 класса» (зажигается
ачивка-strip, флаг localStorage math6_course_done). Тесты math6: 17/17.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:11:15 +03:00
Maxim Dolgolyov 21853bdc27 feat(math6): Глава 4 — Рациональные числа (§1–§11 + финал)
§1 числа со знаком + координатная прямая; §2 модуль, противоположные,
диаграмма N⊂Z⊂Q; §3 сравнение (прямая + наибольшее из трёх);
§4 сложение (демонстратор на прямой + тренажёр); §5 вычитание = +противоп.
(тренажёр + перепиши сложением); §6 законы сложения (удобный счёт +
определи закон); §7 умножение (таблица знаков + тренажёр); §8 деление
(тренажёр + знак частного); §9 порядок действий; §11 прикладной
(температуры/долги/глубина); финал — 6 боссов. ВСЕ 6 глав готовы.
Тесты math6: 17/17.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:08:48 +03:00