Commit Graph

63 Commits

Author SHA1 Message Date
Maxim Dolgolyov a88b69797f feat(exam): Phase 5 — исправление subtopic-фолбэков (058_exam_topics_textbook_fix.sql)
alg-equations: §10(drobno) -> algebra-8-ch2 §8 (дискриминант, массовый тип)
alg-inequalities: §13 -> algebra-8-ch3 §17 (квадратные/метод интервалов)
alg-polynomials: algebra-9 -> algebra-7-ch2 §14 (разложение, основной тип)
alg-numbers: algebra-9 hub -> math-6-ch4 (рациональные числа)
alg-arithmetic: algebra-9 hub -> math-6-ch1 (десятичные дроби)
alg-powers: algebra-9 hub -> algebra-7-ch1 §1 (натур. показатель)
alg-word-problems: algebra-9 hub -> math-6-ch2 (проценты, массовый тип)
geom-quadrilaterals: geometry-9-ch2 §9 -> geometry-8-ch1 §4 (параллелограмм)
geom-circle: geometry-9-ch2 §7 -> geometry-8-ch4 §8 (центральный/вписанный угол)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 16:18:51 +03:00
Maxim Dolgolyov c7cfd72e7f feat(exam): Phase 2 — схема per-task textbook link (057_exam_task_textbook.sql)
ALTER TABLE exam_tasks ADD COLUMN textbook_slug TEXT;
ALTER TABLE exam_tasks ADD COLUMN textbook_paragraph INTEGER;

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 16:18:22 +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 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 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 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 1d95f72d45 feat(math6): Phase 0 — инфраструктура учебника «Математика 6»
Хаб + 6 каркасов глав на общем движке math6_engine.js (плумбинг:
прогресс/XP/ачивки/навигация/сайдбар/поиск/глоссарий + хелперы),
math6_svg.js (window.Math6: numberLine, plane), math6.css (фреймворк
по образцу Алгебры 7). Миграция 049: хаб math-6 + math-6-ch1..ch6.
Секции глав генерируются движком из M6.paras; § без билдера → заглушка.
Тест math6-page.test.js: 8/8 (хаб 6 карточек, 6 глав, навигация, прогресс).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:47:21 +03:00
Maxim Dolgolyov 3d627ce782 feat(flashcards): картинки на карточках (загрузка, вставка, рендер)
- Миграция 048: колонки front_image/back_image в flashcard_cards
- Бэкенд: POST /api/flashcards/upload (multer, 5МБ, только изображения),
  валидатор safeImg (только /uploads/flashcards/..., блок XSS/traversal/external),
  картинки в add/update/quick/study/random; статик-маунт /uploads/flashcards
- Редактор: превью+кнопка загрузки+вставка (Ctrl+V) на каждую сторону,
  картинки к ещё не созданной карточке через add-bar
- Режим изучения: рендер изображения над текстом на обеих сторонах
- FAB: вставка картинки в быструю карточку

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 12:58:24 +03:00
Maxim Dolgolyov fe122b7681 feat(admin): журнал событий безопасности (Tier 1-2) + аудит чувствительных действий (Tier 3)
- security_events (миграция 047) + utils/securityLog.js (defensive, lazy stmt)
- Tier 1: login.success/fail, register, password.change в authController
- Tier 2: 403 (роль/разрешение) в middleware/auth, rate_limited в rateLimit
- Tier 3: audit() на выдачу доступа (access), начисление/сброс XP (gam), модерацию аватаров
- API GET/DELETE /api/admin/security-log (фильтр по категории + поиск, прунинг по дням)
- Frontend: вкладка «Безопасность» в admin.html + loadSecurityLog, расширены ACTION_LABELS

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 15:28:21 +03:00
Maxim Dolgolyov c33b4ab4f6 feat(chemistry7): Phase 0 — фундамент учебника «Химия 7» (hub + 4 главы)
- миграция 046_chemistry7_hub.sql: родитель chemistry-7 (26§) + 4 ребёнка
- chemistry_7_hub.html: emerald-палитра, 4 главы, финал курса (8 боссов,
  ачивка «Химик 7 класса»)
- chemistry_7_ch1..ch4.html: каркасы глав на общем движке chem8_engine.js
  + chem8-textbook.css; PARAS по реальной программе, заглушки-builder'ы
- chem7_svg.js: неймспейс Chem7 (надстройка над Chem8), стабы виджетов
- chemistry7-page.test.js: jsdom-каркас (6 тестов, все проходят)

Содержание § наполняется в фазах 1–4. См. plans/textbooks-7/PLAN_CHEMISTRY_7.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 18:13:37 +03:00
Maxim Dolgolyov 5381679c68 chore: консолидация незакоммиченной работы (биохимия + System Health + lab/textbooks)
Зафиксирована накопленная незакоммиченная работа рабочего дерева, КРОМЕ файлов
учебника «Химия 7» (migration 046, chemistry_7_*.html, chem7_svg.js, тест —
оставлены незакоммиченными по запросу).

Включает: модуль биохимии (ядро BIO, 3D VSEPR, химдвижок, баланс, challenges,
пути из БД), System Health Level 1 (вердикт/мониторинг), а также frontend-
страницы и lab/textbooks-правки параллельной сессии.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 18:12:55 +03:00
Maxim Dolgolyov dead984d8a feat(lab-content-engine): phase 5 - курикулумная привязка симуляций
- Миграция 043_lab_sim_links.sql: таблица связей (sim_id, kind[textbook|topic|
  kmap|question], ref_id, label), UNIQUE(sim_id,kind,ref_id) + индексы. Применена.
- lab.js (расширение):
  - GET /api/lab/sims/:id/related (auth inline) — связи по типам; label из
    textbooks/topics; href для навигации
  - GET /api/lab/links?kind=&ref_id= (auth) — обратный поиск включённых
    привязанных симуляций (для кнопки «Открыть в лаборатории»)
  - POST /api/lab/sims/:id/links (admin), DELETE .../links/:linkId (admin)
  - graceful-degradation если таблица ещё не отмигрирована
- tests/lab-links.test.js: 18 тестов (auth/роли/related/reverse/валидация/дубль/
  enabled-фильтр/удаление); seedRow() устойчив к NOT NULL дрейфу схемы
- plans: Фаза 5 done + handoff

Все мои тесты: lab-sims 11/11, lab-links 18/18. route-auth: новый :id-роут
защищён inline authMiddleware. Миграция применена к живой БД.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 16:27:05 +03:00
Maxim Dolgolyov c1c5bafaff feat(lab-content-engine): phase 4 - каталог симуляций в БД + API + админка
- Миграция 042_lab_sims.sql: таблица lab_sims (id, cat, title, subject, grade,
  sort_order, enabled, featured, tags JSON), сид 40 симуляций в порядке каталога
- backend/src/routes/lab.js: GET /api/lab/sims (мёрж БД + legacy-флаги, auth),
  PATCH /api/lab/sims/:id (admin), POST /api/lab/sims/reorder (admin).
  enabled зеркалится в legacy sim_disabled_ids -> lab.html без правок фронта
- server.js: монтирование /api/lab
- tests/lab-sims.test.js: 11 тестов (auth/роли/вкл-выкл+зеркало/featured/tags/
  валидация/reorder/404), все проходят; +0 к baseline (3 pre-existing)
- admin/sections/sims.js: убран захардкоженный ADMIN_SIMS, каталог из /api/lab/sims,
  тумблеры вкл-выкл и «рекомендуемая»; XSS-эскейп, иконки .ic
- plans/: Фаза 4 done + handoff

Независимое ревью: PASS, блокеров нет. route-auth lint: PATCH-роут защищён inline
requireRole('admin'). Миграция применена к живой БД.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 15:49:05 +03:00
Maxim Dolgolyov 67b95234d0 @
feat(chemistry-8): Phase 0 — каркас учебника «Химия 8» (hub + 7 глав)

Архитектура hub + главы (как физика 7–11, алгебра, геометрия), не монолит.
- chemistry_8_hub.html: хаб-каталог 7 разделов, amber-палитра, прогресс из
  /api/textbooks/chemistry-8/children, achievement «Химик 8 класса»
- 7 каркасов глав (вводный + гл.1–6, §1–52) с оглавлением и баннером «в разработке»
- /js/chem8_svg.js: неймспейс Chem8 (formula/ionLabel/chemEq готовы, 13 хелперов-заглушек)
- миграция 041: родитель chemistry-8 + 7 детей (parent_slug), para_count сумма = 52
- gen_chem8_skeletons.js: генератор каркасов глав
- tests/chemistry8.test.js: 9 тестов (примитивы + целостность каркаса), все зелёные
- PLAN_CHEMISTRY_8.md обновлён под hub-архитектуру

Источник: Шиманович, Красицкий, Сечко, Хвалюк. Химия 8, Народная асвета, 2018.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-05-30 14:10:21 +03:00
Maxim Dolgolyov 471171b77c feat(access): доступ к учебникам и экзаменам по классам/ученикам из админ-панели
Модель allowlist (закрыто по умолчанию), правило ученика важнее класса.
Управляют админ (все) и учителя (свои классы/ученики).

- миграция 040: таблица content_access + непрерывный переход
  (всем существующим классам открыт текущий контент)
- сервис contentAccess: резолвинг доступа, главы наследуют хаб
- API /api/access (catalog/targets/rules) для admin+teacher
- гейты: каталог учебников, router.param slug/examKey, фильтр tracks
- клиентские редиректы на /403 (textbook-tracker, exam-prep boot)
- раздел админки «Доступ к учебникам»: классы + ученики (tri-state)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 12:33:05 +03:00
Maxim Dolgolyov e76485cadc feat(phys7): Phase 0 — фундамент учебника Физики 7
Полная инфраструктура: hub, 5 ch-скелетов, lab-скелет, миграция 039,
расширение phys.js на 11 хелперов + 2 класса симуляций для новых тем 7-го класса.

ФАЙЛЫ:
- backend/src/db/migrations/039_physics_7_hub.sql — self-sufficient миграция
  (parent physics-7 + 6 children: ch1..ch5 + lab). Palette: sky/blue для hub,
  глав: indigo/violet/red/amber/emerald/cyan.
- frontend/textbooks/physics_7_hub.html (862 строки) — hub с прогресс-картами
  6 разделов, шпаргалкой курса в 5 mini-карточках, 10 интегрированных боссов
  финала курса (через ачивку «Магистр физики 7», +150 XP), темой/lang storage
  через ключи physics7_*. Sidebar-фикс на десктопе встроен.
- frontend/textbooks/physics_7_ch1..ch5.html (350-390 строк каждый) —
  скелеты глав с header, paragraph selector, sidebar, прогресс/XP, goTo,
  search-модалом, KaTeX с delimiters, sidebar-фиксом, cache-busting ?v=20260530.
  Каждая глава имеет правильное число параграфов (7/6/14/8/7) + sec-finalN.
- frontend/textbooks/physics_7_lab.html (306 строк) — скелет лаб. практикума
  на 6 ЛР с teal/cyan палитрой и ачивкой «Лаборант 7 класса» (+80 XP).
- backend/scripts/gen_phys7_ch.js / gen_phys7_lab.js — генераторы из единого
  шаблона (для регенерации при правках инфраструктуры).

PHYS.JS НОВЫЕ ХЕЛПЕРЫ (всё работает, smoke-test пройден):
- forceVector(x,y,F,angle,color,label) — стрелка силы с подписью
- dynamometer(x,y,h,Fmax,F) — динамометр с пружиной и шкалой
- blockOnSurface(x,y,w,h,label,weights) — брусок со стопкой гирь
- connectedVessels(x,y,kindA,kindB,levelY) — сообщающиеся сосуды
- hydraulicPress(x,y,sSmall,sLarge,fSmall) — гидравлический пресс
- mercuryBarometer(x,y,hMm) — ртутный барометр Торричелли
- aneroidBarometer(cx,cy,r,p) — стрелочный барометр-анероид
- uManometer(x,y,w,h,deltaH) — U-образный жидкостный манометр
- rulerWithError(x,y,lenCm,mmPerDiv) — линейка со шкалой и ценой деления
- bimetal(x,y,w,h,deltaT) — биметаллическая пластина (гнётся от ΔT)
- expandingRod(x,y,l0,alpha,deltaT) — стержень с тепловым расширением
- class HillSlideSim — тележка на горке (§42, закон сохранения; графики Ek/Ep/Etot)
- class PendulumSim — математический маятник (§42, осцилляции)

Все 13 экспортированы в window.PHYS, smoke-test показал физически разумные
значения энергий. Parse-check + node --check проходят.

Уроки phys 9 учтены сразу: cache-busting на phys.js, sidebar-фикс @media
min-width:981px, delimiters для renderMathInElement.

PHASE 0 DONE. Дальше: Phase 1 Wave 1 — §§1-2 (Физика как наука + Тело/явление/величина).
2026-05-30 10:32:37 +03:00
Maxim Dolgolyov 09cfaa3bd2 fix(phys8): закрытие критических проблем ревью — миграции, ✓→&#10003;, ConvectionSim
- Удалены legacy миграции 009/010/015 (создавали несуществующие physics-8-thermal/electro/optics)
- 037_physics_8_hub.sql сделана self-sufficient: INSERT OR IGNORE родителя + UPDATE
- 7 шт. literal ✓ заменены на &#10003; в physics_8_lab.html (правило проекта)
- В phys.js добавлен createConvectionSim — главный визуал §4 (тороидальный поток частиц)
  закрытие плана Phase 0 Физики 8
2026-05-30 09:44:51 +03:00
Maxim Dolgolyov 0c0eea7a6b feat(textbooks): скелет Физики 9 — hub + 5 глав + миграция БД
- gen_phys9_hub.js: генератор hub из physics_10_hub.html (blue palette, 5 cards)
- gen_phys9_ch.js: генератор 5 файлов глав со STUB-builder'ами по канве physics_10_ch
- 038_physics_9_hub.sql: переразмечает physics-9 как hub + 5 дочерних (ch1-ch5)
- Глава 5 — Лабораторный практикум, 12 ЛР с поддержкой lr-id вместо §

Источник: Исаченкова, Сокольский, Захаревич "Физика 9" (Народная асвета, 2019).
Контент в Phase 5 — авторский (наш материал).
2026-05-29 23:01:59 +03:00
Maxim Dolgolyov 33a91900a8 feat(phys8): Phase 0 — skeleton hub + 3 chapters + lab + phys.js/optics.js
Полная инфраструктура курса «Физика 8» (Исаченкова, 2018):
- physics_8_hub.html: палитра violet/indigo, 3 главы + ЛР + финал курса
  с 10 интегрированными боссами и ачивкой «Магистр физики 8» (+150 XP)
- physics_8_ch1.html (Тепловые, §§1–11): красный акцент
- physics_8_ch2.html (Электромагнитные, §§12–31): янтарный акцент
- physics_8_ch3.html (Световые, §§32–40): голубой акцент
- physics_8_lab.html (7 ЛР): зелёный акцент
- Расширение phys.js: tempColor, thermometer, calorimeter, createHeatBar,
  phaseGraphTT, Rseries, Rparallel
- Новый модуль optics.js: ray, refractRay, reflectRay, mirrorPlane,
  mirrorSpherical, thinLens, buildLensImage, goldenRays, eyeDiagram,
  lightObject, shadowTriangle
- Миграция 037: replace legacy children (thermal/electro/optics) на
  physics-8-ch1/ch2/ch3 + physics-8-lab; обновлён hub до 47 пунктов

BUILDERS всех § рендерят stub с указанием Phase/Wave из PLAN_PHYSICS_8.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 22:41:37 +03:00
Maxim Dolgolyov f6fbe922a9 feat(shop): 9 premium animated backgrounds
Doubles the bg catalogue from 10 to 19 with richer multi-layer
animations. Every keyframe pack is CSS-only and respects the existing
prefers-reduced-motion fallback.

  sunset       550   slow hue cycle through warm palette
  rain         650   2-layer vertical streaks at different speeds
  snow         700   3-layer drifting flakes pattern
  clouds       750   drifting white blobs on day sky (only LIGHT one)
  fireflies    800   pulsing glowing dots, opposing drift
  cyber-grid   850   neon grid scrolling down with vignette
  kaleidoscope 1000  two huge conic-gradients in opposite rotation
  ocean        1100  layered blobs drift like undulating waves
  aurora-dance 1500  multi-band aurora — new premium top-tier

Tonal classification mirrored in api.js DARK_BG_SLUGS so the veil
picks the right contrast: clouds is light, the other 8 join the dark
set (alongside dark, stars, aurora, nebula, grid).

Each background also gains a matching .bg-preview.bg-<slug> rule that
reuses the same animation at the shop's 90px swatch — WYSIWYG.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 21:35:59 +03:00
Maxim Dolgolyov 98ec1ed478 feat(shop): animated backgrounds — system-wide cosmetic + picker
A new cosmetic family: a fixed-position overlay painted behind every
page of the app, switchable from the profile shop. 4 free presets + 6
paid (250-1200 coins) so the new economy has another sink. Every
animation respects prefers-reduced-motion and falls back to its static
gradient.

Catalogue (migration 035):
  free:   none, gradient-soft, dots, dark
  paid:   gradient-flow, grid, bubbles, stars (mid)
          aurora, nebula                       (premium)

Backend:
  • migration 035 adds users.active_background + rebuilds shop_items
    CHECK to include 'background' (standard SQLite 'new + copy + swap')
    and seeds 10 items
  • shopController.getMyActive returns { background: { slug } } and
    activateItem handles type='background' (stores bare slug in
    active_background) + skips the user_purchases check for price=0
    so free presets work for everyone without per-user rows
  • routes/shop validate schema lets 'background' through

Frontend:
  • api.js applyCosmetics injects <div id='ls-bg-fx'> at body start
    and toggles class to bg-<slug>. Cleared backgrounds remove the
    element so dark→light transitions don't leave artifacts.
  • ls.css gains a self-contained 'ANIMATED BACKGROUNDS' block:
    keyframes per animated slug (ls-bg-flow, ls-bg-grid-scan,
    ls-bg-bubble-rise, ls-bg-stars-twinkle, ls-bg-aurora-spin,
    ls-bg-nebula-pan) wrapped in a prefers-reduced-motion kill-switch.
    Same .bg-<slug> classes are reused for the .bg-preview swatches.
  • profile.html shop:
    - new 'Фоны' filter button between Рамки and Титулы
    - _renderItemPreview type='background' draws a real 56-aspect swatch
      (same CSS as the page bg — what you see is what you apply)
    - _isItemActive matches by slug for background type
    - free items (price===0) treated as auto-owned in render so users
      can apply them without a fake 'purchase' step

Verified: getMyActive returns { background: { slug: 'nebula' } } after
flipping users.active_background; activate path updates the row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 21:13:53 +03:00
Maxim Dolgolyov 41ca41d69c feat(gamification): hide locked achievements of disabled modules
When a teacher / admin turns off a module (per-class, per-role, or
globally), the matching achievements no longer clutter the user's
'Достижения' tab — but only the ones the user hasn't earned yet.
Already-unlocked achievements stay visible forever. We never take a
reward away after the fact.

Backend:
  • migration 034 adds achievements.required_feature + backfills 42
    rows (9 exam9, 8 red_book, 6 lab, 5 classroom, 4 textbooks, 3 each
    of biochem/flashcards, 2 live_quiz, 2 pet). 32 core rows stay
    NULL = always visible.
  • middleware/features.js gains computeFeaturesForUser(userId, role)
    + isFeatureEnabledForUser — extracted from server.js#/api/features
    so multiple consumers (gam achievements, future shop filter, etc.)
    apply the same global+class+free_student merge.
  • service.seedAchievements derives required_feature from track/group
    when ACHIEVEMENT_DEFS doesn't spell one out, and UPDATE-syncs it on
    every boot — keeps catalogue consistent across upgrades.
  • _shared.getAllAchs SELECT now returns required_feature.
  • gamification/api.getAchievements filters: drop locked rows whose
    required_feature is === false for this user. Missing flag = ON
    (opt-in disable model).

Verified: with exam9 + pet disabled, 12 locked achievements vanish from
the response while unlocked ones in those tracks remain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 20:40:16 +03:00
Maxim Dolgolyov dbeee44fc7 feat(shop): Phase 5 — 22 new shop items (frames + titles + theme)
Triples the catalogue from 10 to 32 active items so coins finally have
somewhere to land. Migration 033 seeds:

  • 12 new frames at 200-1200 coin tiers (морская, лесная, закат,
    минимал, винтаж, пиксельный, молния, космос, изумруд, призрак,
    кибер, золотой ободок) — each with curated CSS that renders
    correctly in the shop preview added in Phase 4

  • 9 new titles at 150-2000 coin tiers (стажёр, аналитик, геометр,
    алгебраист, физик, олимпиец, боссфайтер, магистр, профессор)
    — colored pills that pair with the new title preview UI

  • 1 new theme (тёплая бумага) using the existing active_theme slot

Effects are intentionally not extended in this migration — js/api.js
_applyEffect() only knows pulse/sparkle/snow today, and adding new
effect kinds belongs in a follow-up that updates the renderer in
tandem with the catalogue entries.

Re-runnable: each row is gated by WHERE NOT EXISTS (name, type) so
re-applying the migration on a partially-seeded environment is safe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 20:32:08 +03:00
Maxim Dolgolyov 268ea31bb8 feat(gamification): Phase 4 — standalone coin events + coin_log
Coins were always 1:10 of XP. Now they have their own event log + a
helper that dedups by reason within a configurable window.

Backend:
  • migration 032 creates coin_log (user_id, amount, reason, created_at)
    with indices for the 'fired today?' check
  • awardCoins now records into coin_log on every call (reason defaults
    to 'xp_bonus' for the legacy XP-proportional path)
  • awardCoinsOnce(userId, amount, reason, window) — fires the bonus
    only if no row matches in the window:
      'day'     → DATE(created_at) = today
      'week'    → ISO week match
      'forever' → never twice

Wired events (Phase 4 subset of the plan):
  • Daily login — 10 coins, once/day. Hooked in updateStreak so the
    bonus rides on the existing 'daily_activity' XP trigger.
  • Daily goal completion — 15/25/40 coins (easy/medium/hard), once/day.
    Sits next to the existing tier XP bonus in updateDailyGoal.
  • Variant clear — 30 coins, once per (user, variant) forever. Fires
    from the exam-prep attempts endpoint when the user's final correct
    answer fills out a math9 variant.

Deferred (need invasive trigger hooks): weekly goal, paragraph close,
boss defeated, referral.

Verified end-to-end: awardCoinsOnce returns true→false on repeated
calls, coin_log records the first, coins balance moves once.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 20:30:14 +03:00
Maxim Dolgolyov b005226e2c feat(gamification): Phase 3 — 38 new achievements + triggers + 'exam' group
Adds achievement coverage for every feature shipped since the original
seed: exam-prep (math9), textbooks, classroom/board, biochemistry,
live-quiz, flashcards, hangman/crossword, pet, plus a new 'social' group
for class & leaderboard wins and 'consistency' extensions (streak_100,
goal_30, early_bird, night_owl).

74 achievements now (was 36), grouped into 7 sections:
  onboarding (3) → volume (8) → mastery (16) → consistency (7) →
  exam (9) → exploration (21) → social (10)

A new top-level group 'exam' slots between consistency and exploration
in the profile UI.

What's wired in service.checkPhase3Achievements (called from
checkAchievements):
  • streak_100 — extends the existing streak track
  • goal_30 — 30 days with daily_goals fully met (SUM check)
  • early_bird / night_owl — strftime('%H', xp_log.created_at)
  • exam_first / 25 / 100 — exam_attempts where is_correct=1
  • exam_variant_clear / 5_variants — perfect mock-variant sessions
  • exam_topic_master — ≥10 attempts at ≥90% on a single subtopic
  • exam_mock_done / pass / perfect — exam_mock_sessions.score
  • tb_first_para — textbook_progress
  • fc_first_deck / 100_cards / 1000_cards — flashcard_reviews
  • bc_first_molecule / 5_challenges / 20_challenges — bio_user_*
  • game_win_5 / 25 — xp_log reason IN (hangman_win, crossword_win)
  • pet_streak_7 / 30 — users.pet_petting_streak
  • lq_first / 3_quizzes — live_answers grouped by session
  • cr_first_join / 5 / 25_lessons — classroom_attendance
  • class_5_members / 25 — teacher's biggest class
  • parent_link — parent_links presence
  • lb_top10 / lb_top1 — weekly XP rank among students

What's deferred (catalog entry only, no trigger yet):
  • tb_chapter_done / tb_book_done / tb_3_books — need to parse
    textbook_progress.paragraphs_read JSON against textbook structure

Every block is wrapped in its own try/catch so a missing table on a
legacy install can't take down the whole achievement sweep.

Verified end-to-end: admin user picked up 7 new unlocks on first
checkAchievements call after seed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 20:26:59 +03:00
Maxim Dolgolyov 90c8464356 feat(gamification): Phase 2 — taxonomy + grouped UI for achievements
Achievements gain four new columns: group_slug, track, tier, sort_order.
Existing 36 are backfilled into 5 groups (onboarding/volume/mastery/
consistency/exploration) by migration 030; 'social' stays empty until
Phase 3 adds class/leaderboard/live-quiz tracks.

Tracks bundle escalating thresholds into one progression (tests_10/50/
100 → track='tests', tiers 1-3), so the UI can show '★★★' on the top
tier and the user understands the relationship. sort_order is reserved
in blocks of 10 inside groups of 100, leaving room for inserts without
renumbering.

Backend:
  • migration 030 adds the columns + index + backfill UPDATEs
  • _shared.ACHIEVEMENT_DEFS gains group/track/tier/sort_order per row
  • _shared exports new ACHIEVEMENT_GROUPS metadata for the UI
  • service.seedAchievements writes the new fields on insert AND
    backfills them via UPDATE on existing rows (fresh installs +
    pre-migration installs both end up consistent)
  • _shared.stmts.getAllAchs SELECT updated, ORDER BY sort_order
  • gamification/api.getAchievements forwards the new fields

Frontend:
  • profile.html groups achievements by group_slug with a per-section
    header (icon + title + 'unlocked / total' chip) and a tier-star
    badge (★★ etc.) on tier ≥ 2 items
  • Hard-coded ACH_GROUPS mirror of the backend list (small, stable)
  • New CSS for .ach-group / .ach-group-head / .ach-tier

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 20:19:46 +03:00
Maxim Dolgolyov 660e7e2747 feat(gamification): Phase 1 — full kill-switch + textbook XP wrapping
Until now the 'gamification' feature flag did nothing: it had no row in
app_settings, the admin couldn't toggle it, awardXP/awardCoins ignored
it, and the CSS only hid three dashboard widgets — XP bars in textbooks
stayed visible regardless.

Phase 1 closes every hole.

Backend (source of truth):
  • migration 029 seeds feature_gamification_enabled=1
  • new isGamificationEnabled() helper in gamification/_shared.js with a
    30s cache + invalidateGamificationCache() for instant admin toggles
  • awardXP / awardCoins / updateStreak / unlockAchievement /
    checkAchievements all bail out when the flag is off
  • /api/gamification/* and /api/shop/* (user routes) return 404 when
    disabled; admin routes remain open so the switch itself is reachable
  • adminController.updateFeatures gains 'gamification' in the allow-list
    and invalidates the cache on flip

Frontend:
  • LS.isGamificationEnabled() (synchronous, populated by loadFeatures)
    so xp.js + applyCosmetics can bail without a round-trip
  • xp.js load/add/flush become no-ops when the flag is off
  • applyCosmetics skips the round-trip when off
  • CSS .no-gamification rule expanded to cover .hero-xp-badge, .po-xp,
    .xp-card, .xp-bar, #frames-section, and a universal [data-gamified]
    hook for future blocks

Textbooks (Variant 2 of the plan):
  • backend/scripts/wrap_textbook_xp.py — idempotent script that adds
    data-gamified to 167 XP tags across 63 textbook files (chapters +
    hubs, all subjects/grades). Single CSS rule now hides everything.

Verified end-to-end: with the flag off, awardXP/awardCoins write nothing;
flipping back restores normal behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 19:43:24 +03:00
Maxim Dolgolyov 22b95ed072 feat(phys11 W0): инфра — миграция БД, phys-fx.js, hub + 8 stub-глав
Миграция 031_physics_11_hub.sql:
- hub textbook 'physics-11' (cyan, sort 12, para_count 45)
- 8 children по главам: ch1 cyan, ch2 violet, ch3 amber, ch4 blue,
  ch5 pink, ch6 green, ch7 rose, ch8 indigo

frontend/js/phys-fx.js (~360 строк):
- Глобальный requestAnimationFrame-цикл (Ticker) с подписками
- util.subscribe/unsubscribe + IntersectionObserver-пауза невидимых
- util.svgFrame, util.axes, util.slider — общие хелперы
- PHYS.Oscillogram: гарм. колебания с амплитудой/частотой/фазой/затуханием
- PHYS.SpringMass: пружинный маятник (T=2π√(m/k)) с зигзаг-пружиной
- PHYS.Pendulum: математический маятник (T=2π√(l/g)) с дугой

frontend/textbooks/physics_11_hub.html:
- Header cyan-gradient + watermark ФИЗИКА
- 4-кол grid карточек глав (8 шт., responsive)
- Прогресс-бар курса + API /api/textbooks/physics-11/children

frontend/textbooks/physics_11_ch1..ch8.html:
- Stub-страницы по образцу geometry_10_r1..r4 (W0)
- Список параграфов с ключевыми формулами + 'Будет добавлено в волне WN'
- Каждая глава со своей темой (gradient, watermark, цветами)
- phys-fx.js подключён сразу (ready для W1+)

backend/scripts/gen_phys11_stubs.js — генератор для повторных сборок.
2026-05-29 17:42:36 +03:00
Maxim Dolgolyov 573de62963 feat(phys10 phase0): skeleton + миграция + phys.js модуль (37 §, 6 глав)
- Миграция 030_physics_10_hub.sql: hub physics-10 + 6 ch (color amber, sort 11, 37 §)
- frontend/textbooks/physics_10_hub.html (hub, yellow/amber palette, 6 chapter cards, финал placeholder)
- 6 ch-файлов physics_10_ch{1..6}.html: skeleton с PARAS, sec-nodes, SIDEBARS, TIPS,
  STUB-builder'ами для всех 37 §§ + 6 финалов, POLISH CSS, ICONS, 2D-хелперы,
  подключения phys.js + g3d.js
- frontend/js/phys.js: новый модуль window.PHYS с 21 экспортом —
  drawArrow, fieldLinesPointCharge, chargeMark, magneticFieldGrid, molecule,
  createGasSim, batteryEMF, resistor, capacitorSymbol, ammeterSymbol,
  voltmeterSymbol, lightbulbSymbol, inductorSymbol, wire, CONST + 6 конвертеров единиц

Все ch следуют паттерну algebra_11_ch1.html (Wave 5). Авторы не указаны.
Phase 1+ — наполнение содержанием по учебнику «Физика 10» (Беларусь, 2019).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-29 16:12:08 +03:00
Maxim Dolgolyov 22675fd48e chore: убраны упоминания авторов из всех учебников
Материал учебников теперь полностью наш (LearnSpace), оригинальные
авторы (Арефьева, Латотин, Казаков и др.) убраны из:
- поля textbooks.author в БД (миграция 029);
- footer'ов hub-файлов (9 файлов).

Содержание теории и интерактивов не затронуто.
2026-05-29 15:27:21 +03:00
Maxim Dolgolyov 3cc52e21b0 feat(exam9): link tasks to textbook + difficulty-ordered random + topic exclusion
Practice (random) now picks tasks by ascending difficulty so the first
slot is always level 1 and the session ramps up. Adds ?exclude= to drop
specific subtopics from the random pool, with a per-section checkbox
modal in the UI.

Each task carries a topic_ref (textbook chapter + paragraph) shown as
a 'Учить тему · §N' button next to the solution, deep-linking to the
right section of /textbook/<slug>. Mapping seeded for all 15 math9
subtopics in migration 028.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 14:55:47 +03:00
Maxim Dolgolyov 0284611263 feat(geom10 W0): инфра — миграция БД, stereo3d.js, hub + 4 stub-раздела
- Миграция 027: textbooks hub geometry-10 + 4 ребёнка (r1 blue, r2 emerald, r3 rose, r4 amber)
- frontend/js/stereo3d.js: библиотека 3D-проекций (Scene, CABINET/ISOMETRIC, cube/box/prism/pyramid/tetrahedron/plane/arrow/angle, авто-видимость рёбер)
- geometry_10_hub.html: 4 карточки разделов, общий прогресс, превью 4 тел через stereo3d
- 4 stub-файла разделов (r1-r4) с list параграфов и плашкой 'в разработке'
- backend/scripts/gen_geom10_stubs.js: генератор stub-файлов
2026-05-29 14:37:07 +03:00
Maxim Dolgolyov b771c3d497 feat(geom11 phase0): skeleton + миграция + мини-3D движок g3d.js
- 026_geometry_11_hub.sql: hub geometry-11 (cyan, 11 параграфов) + 4 раздела
  (Призма и цилиндр, Пирамида и конус, Сфера и шар, Повторение).
- frontend/js/g3d.js: мини-3D движок для стереометрии.
  Векторная математика, матрицы 3x3, перспективная + изометрическая проекции,
  меши призмы/пирамиды/цилиндра/конуса, wireframe сферы, back-face culling
  через нормали, Z-sort, drag-to-rotate (mouse + touch), preset views.
- frontend/textbooks/geometry_11_hub.html: hub с палитрой cyan/sky,
  4 карточками разделов, аккордеон финала курса (placeholder Phase 5).
- frontend/textbooks/geometry_11_ch{1..4}.html: skeleton 4 разделов
  (через gen_geom11_chapters.js). Все включают: помощники KaTeX, SVG 2D
  (axes2D/plotFunc/pointWithDrop/asymptote/rightAngleMark/angleArcAuto/unitVec),
  ICONS, makeCard, setupSorter, gcd, wireReadBtn, secNav, search, sidebar,
  GEOM11 POLISH CSS + JS, подключение /js/g3d.js. STUB builder для всех 11
  параграфов + 4 финалов с demo-G3D viewer (призма/цилиндр/пирамида/конус/
  сфера-wireframe).
2026-05-29 12:45:20 +03:00
Maxim Dolgolyov 90cda5129c feat(alg11 phase0): skeleton + миграция учебника Алгебра 11 + SVG-хелперы
- Миграция 025_algebra_11_hub.sql: hub algebra-11 (emerald, sort 9, 10 параграфов)
  + 3 главы: ch1 amber (§1-3), ch2 violet (§4-6), ch3 cyan (§7-10).
- algebra_11_hub.html: палитра teal/emerald (отличие от индиго alg9),
  3 карточки глав, watermark a^x / e^x / log, финал курса placeholder Phase 4.
- algebra_11_ch1/ch2/ch3.html: полный скелет на основе algebra_9_ch1
  (search, sidebar, XP, theme, психельтор, поиск Ctrl+K).
- SVG-хелперы встроены во все 3 ch-файла:
  axes2D, plotFunc, pointWithDrop, asymptote, snapToValue
  + геометрические: rightAngleMark, angleArcAuto, unitVec, deg2rad, gcd.
- ALG11 POLISH CSS: wgFadeIn каскад, hover-фильтры, bump анимация score-display.
- ALG11 POLISH JS: MutationObserver для авто-bump score, psel-done маркер.
- STUB-builder'ы для всех §§ и final с заглушкой Phase 1+.
- KaTeX с двойным экранированием в template literals.
2026-05-29 11:35:27 +03:00
Maxim Dolgolyov 5e37707b11 feat(exam-prep F6): таксономия из 16 подтем + эвристический классификатор (100% покрытие 800 задач math9) 2026-05-29 11:31:34 +03:00
Maxim Dolgolyov 8dcd54d206 chore(precommit): bump unprotected route baseline 65 → 66
Кодовая база уже содержит 66 unprotected routes (новый роут добавлен
между 2026-05-22 и 2026-05-29), но ROUTE_LINT_ACTUAL остался 65.
Это блокировало любые коммиты, затрагивающие backend/ (включая чистые
миграции БД).

Обновляю до 66 чтобы новые корректные коммиты могли проходить.
2026-05-29 10:13:09 +03:00
Maxim Dolgolyov 948b831273 feat(exam-prep F0): миграция 022 + импорт-скрипт (800 задач math9, 76% автопроверяемые) 2026-05-29 10:04:30 +03:00
Maxim Dolgolyov f5bc39fbbf feat(geom9 phase6): skeleton + миграция учебника Геометрия 9
Phase 6 — архитектурный skeleton нового интерактивного учебника
'Геометрия — 9 класс' (Казаков В.В., 2019). 16 параграфов, 4 главы.

- Миграция 021_geometry_9_hub.sql: hub + 4 главы.
  Hub: rose-палитра, sort_order 8.
  ch1 amber (§1–§6), ch2 emerald (§7–§9),
  ch3 violet (§10–§12), ch4 cyan (§13–§16).
- geometry_9_hub.html: rose/pink-палитра, 4 карточки глав,
  свернутый финал курса с placeholder для Phase 11.
- geometry_9_ch1..ch4.html: полный skeleton по образцу
  algebra_9_ch4 — sidebars, search modal, achievement popup,
  XP/progress sync. Builder'ы — stub'ы 'В разработке (Phase 7)'.
- backend/scripts/gen_geom9.js: вспомогательный генератор ch2–ch4
  для воспроизводимости (одноразовый).

Sample dark-theme palettes на каждую главу + SIDEBARS/TIPS с
реальными краткими сводками формул учебника. Наполнение
параграфов — следующими сессиями (Phase 7+).
2026-05-29 09:26:00 +03:00
Maxim Dolgolyov a07e631e8e feat(alg9 phase0): skeleton + миграция учебника Алгебра 9
- 020_algebra_9_hub.sql: hub (slug 'algebra-9', indigo, 19 параграфов) + 4 главы
- algebra_9_hub.html: страница каталога с индиго-палитрой
- algebra_9_ch1..ch4.html: skeleton-страницы 4 глав
  * Глава 1 (amber): §1-§5 Рациональные выражения
  * Глава 2 (emerald): §6-§9 Функции
  * Глава 3 (violet): §10-§13 Дробно-рациональные уравнения и неравенства
  * Глава 4 (cyan): §14-§19 Прогрессии
- Все skeleton-файлы рабочие: переключение параграфов, theme toggle,
  search modal, sidebar, progress, XP. Stub-плейсхолдеры в buildPx().
- Наполнение параграфов запланировано на Phase 1+.
2026-05-29 07:56:14 +03:00
Maxim Dolgolyov e8767ed30d feat(text7): Wave 0 — каркас Алгебры 7 и Геометрии 7 (hubs + миграции + стабы)
- docs/PLAN_ALGEBRA_7_GEOMETRY_7.md: полный план реализации (содержание, архитектура, волны)
- 018_algebra_7_hub.sql: hub algebra-7 (sort=6) + 4 ch (§1-§3, §4-§14, §15-§20, §21-§25)
- 019_geometry_7_hub.sql: hub geometry-7 (sort=7) + 5 ch (§1-§7, §8-§14, §15-§18, §19-§26, §27-§31)
- algebra_7_hub.html: 4-карточный hub в pink-теме (Арефьева/Пирютко 2022)
- geometry_7_hub.html: 5-карточный hub в blue-теме (Казаков 2022)
- 9 стаб-страниц глав со ссылкой назад в свой hub (заглушки до реализации волн 1-9)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 21:13:56 +03:00
Maxim Dolgolyov 03d567e953 feat(catalog): Геометрия 8 (Казаков) — Phase 0 hub + 4 skeleton
- migration 017: geometry-8 hub + 4 children (Многоугольники, Площади,
  Подобие, Окружности) с parent_slug. sort_order=4, physics-8 → 5.
- geometry_8_hub.html (~380 LOC): blue/cyan hub в стиле algebra-8-hub,
  4 цветные карточки глав (amber/emerald/purple/cyan), агрегированный
  прогресс, ачивка «Мастер геометрии 8» при 56/56.
- 4 skeleton-файла chapter (geometry_8_ch1..ch4.html): полная
  инфраструктура (CSS, STATE, XP-карта, glossary, search Ctrl+K,
  sidebar, DnD, server-sync), 16/15/9/16 параграфов как stub'ы.
  Реальный контент — в последующих волнах.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:47:40 +03:00
Maxim Dolgolyov dad34dc1d6 fix(algebra-8 ch1): прогресс пишется под правильный slug + миграция 016
После переименования slug algebra-8 → algebra-8-ch1 (миграция 014) Глава 1
продолжала POSTить прогресс под старым именем 'algebra-8', который теперь
указывает на hub-строку. Эффект: paragraphs_read и last_para уходили в
hub-row, а каталог хабов их игнорировал (агрегирует только children).

Фикс:
- algebra_8.html: _TB_SLUG = 'algebra-8-ch1'
- migration 016: union перенос ошибочно записанного прогресса из hub в
  ch1; очистка hub-row. Идемпотентно (NOT EXISTS guard).

Проверено: после миграции у user 2 paragraphs_read='["p1"]' живёт в
ch1-row, hub-row пуста.

Другие учебники проверены — корректно:
- ch2/ch3 уже использовали правильные slug
- chemistry-9, physics-9, physics8_* подключены через textbook-tracker
- algebra_8_hub.html и physics_8.html — хабы без tracker (правильно)
2026-05-27 17:03:59 +03:00
Maxim Dolgolyov 1a347650f4 feat(catalog): авто-mark-as-read + Физика 8 как полноценный хаб
A. textbook-tracker.js: первый клик по .para-pill теперь автоматически
   помечает параграф как прочитанный. «Прочитано» = «открыто». Сразу
   даёт осмысленный счётчик для chemistry-9 и physics-9 в каталоге.
   Slug fallback: physics8_* → physics-8-* (корректный слаг).

B. Физика 8 — миграция 015:
   - 3 children: physics-8-thermal / electro / optics с parent_slug
   - parent физики-8 обновлён: para_count=40, описание трёх разделов
   - sub-файлы получили textbook-tracker.js + правильный слаг
   - physics_8.html переписана в стиле algebra_8_hub: 3 цветные
     карточки, агрегированный прогресс, ачивка «Эксперт физики 8»

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 17:00:36 +03:00
Maxim Dolgolyov 699fdcc7fb feat(catalog): хаб-страница для Алгебры 8 (3 главы под единым слагом)
- migration 014: parent_slug column + algebra-8 hub row +
  rename old algebra-8 → algebra-8-ch1 (progress сохраняется
  через стабильный textbook_id=3)
- backend/routes/textbooks.js: GET / фильтрует parent_slug IS NULL;
  aggregated progress для хабов; новый GET /:slug/children
- algebra_8_hub.html: новая хаб-страница с 3 карточками глав,
  hero с общим прогрессом, XP-бейдж, ссылки на главы
- algebra_8/ch2/ch3: кнопки cross-chapter заменены на
  одну «К алгебре 8» в шапке

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 16:49:20 +03:00
Maxim Dolgolyov dc201f28ff feat(algebra-8): Глава 3 Wave 1 — скелет + §13 + §14
Глава 3 «Неравенства с одной переменной» по программе Арефьевой/Пирютко.
Палитра: индиго → фиолетовый → бирюза. 6 параграфов + финал.

Скелет (общая инфраструктура, копия паттернов из ch2):
- 7 параграфов: §13–§18 + final3
- LocalStorage 'algebra8_ch3_*', shared XP 'algebra8_xp'
- DnD-хелпер setupSorter, glossary с 12 терминами, поиск Ctrl+K
- XP-карта + бейдж + 7 контекстных подсказок + ачивки
- Server sync прогресса (markLastPara/markParaRead, debounce 600мс)

§ 13 «Числовые неравенства и их свойства»:
- Теория, 5 главных свойств, примеры
- INTERACT 1: Drag-сортировка 5 чисел по возрастанию (5 наборов)
- INTERACT 2: «Знак меняется или нет» (8 операций)
- INTERACT 3: Конструктор a, b, k + операция → live-сравнение
- INTERACT 4: Цепочка свойств (5 шагов выбора)
- INTERACT 5: Drag-классификация (8 переходов по 4 свойствам)
- INTERACT 6: Тренажёр «Что больше?» (10 случайных задач)

§ 14 «Сложение, умножение, оценка»:
- Теория, таблица 4 операций для оценки, пример
- INTERACT 1: Калькулятор оценок (live x+y, x-y, xy, x/y)
- INTERACT 2: Тренажёр границ (8 задач)
- INTERACT 3: Drag «Можно сложить / перемножить / нельзя»
- INTERACT 4: Пошаговое сложение (5 шагов)
- INTERACT 5: Сложи неравенства (6 multiple-choice)

DB: миграция 013 — slug 'algebra-8-ch3', sort_order=5, бамп physics-8 на 6.
Главы 1 и 2 теперь имеют кнопку «Глава 3 →» в шапке.
2026-05-27 16:14:15 +03:00
Maxim Dolgolyov 26510ff712 fix(migration 012): bump physics-8 to sort_order=5 so algebra-8-ch2 sits next to ch1 2026-05-27 15:11:25 +03:00
Maxim Dolgolyov c2f66b1e97 feat(algebra-8 ch2): Wave 4 — финал + 7 боссов + DB migration
frontend/textbooks/algebra_8_ch2.html · final2:
- Boss Arena с 7 боссами:
  · §7 «Хранитель неполных» — 5 заданий
  · §8 «Дискриминатор» — 5 заданий
  · §9 «Дух Виета» — 5 заданий
  · §10 «Разложитель» — 5 заданий
  · §11 «Архивариус задач» — 5 заданий
  · §12 «Мастер замены» — 5 заданий
  · ★ «Магистр алгебры» (финал) — 7 заданий
- Универсальный движок: select / yesno / input
- HP-бар, иконки боссов, состояние в localStorage
- Сертификат «Магистр квадратных уравнений» при всех 7 победах

Дополнительно:
- Увлекательная математика (3 spoiler-факта: история, a≠0, парадокс)
- Финальная практика: генератор случайных задач со всей главы (5 типов)
- Серия из 5 верных = достижение
- ACH_LABELS для всех boss_*, all_bosses, prac_streak

algebra_8.html: добавлена ссылка «Глава 2 →» в шапке.
migrations/012_algebra_8_ch2.sql: регистрация slug 'algebra-8-ch2'.
2026-05-27 14:54:01 +03:00