Files
Learn_System/plans/lab-content-engine/CONTEXT.md
T
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

117 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Feature Context: Контент-движок лаборатории
## Current State
- Лаборатория работает на захардкоженной регистрации (см. PLAN.md Summary).
- Ветка `feature/lab-content-engine` создана от `master`.
## Architecture map (как было ДО рефактора)
- `frontend/lab.html` — sim-тела `<div id="sim-xxx">` (inline HTML, ~3000 строк) + 58 `<script>` тегов (4800-4861) + three.js.
- `frontend/js/labs/lab-glue.js`:
- `_catFilter`, `_disabledSimIds`, `_simModuleDisabled` (вкл/выкл из админки)
- `filterSims()`, `renderSims()` (карточки каталога)
- preview-хелперы `_grid/_axes/_svg` + ~60 констант `P_*`
- массив `SIMS` (821-866), `window.SIMS`/`window.LAB_SIMS`
- `frontend/js/labs/lab-init.js`:
- объявления переменных симуляций (gSim, pSim, …)
- `ALL_SIM_BODIES` / `ALL_CTRL_BARS` (33-48)
- `_pauseAllSims()` (54-91), `openSim(id)` if-цепочка (93-160), `closeSim()` (212-258)
- `_simShow()`, `_addTouchSupport()` (touch-bridge + ResizeObserver)
- объект `THEORY` + `loadTheory()` + `_theoryToggle()`
- функции `_openXxx()` (603-756) — единый шаблон: `_simShow('sim-xxx')` + ленивое `new XxxSim(...)` + показ `ctrl-xxx`
- `frontend/js/admin/sections/sims.js` — админ-секция (пока только вкл/выкл, `_disabledSimIds`).
## Загрузочный порядок (КРИТИЧНО)
В lab.html: движки `_fx_*`, `_phys_visuals`, `_graph_panel`, `_chem_visuals` грузятся ПЕРЕД симуляциями.
`lab-init.js` (4826) грузится ПЕРЕД `lab-glue.js` (4827). `renderSims()` вызывается в конце lab-glue.
Некоторые sim-файлы (graph.js) грузятся РАНЬШЕ lab-glue.js → preview `P_*` ещё не определены на момент исполнения их тел.
=> В манифестах `preview` поддерживает функцию (ленивое вычисление в renderSims), не только строку.
## Контракт LabRegistry (Фаза 0)
```
LabRegistry.register(manifest) // manifest.id уникален; повторная регистрация перезаписывает
LabRegistry.get(id) // по base-id (без ':arg')
LabRegistry.has(id)
LabRegistry.all() // в порядке регистрации
LabRegistry.setActive(sim) / stopActive() / destroyActive() // менеджер жизненного цикла
```
manifest: `{ id, cat, title, desc, preview(string|fn), theory?, bodyId?, mount?(host), open(ctx), stop?(), destroy?(), subject?, grade?, topics? }`
## Адаптер (Фаза 0): реестр в приоритете, иначе legacy
- `renderSims()` — порядок берём из исходного `SIMS`; для id, который есть в реестре, используем манифест (resolve preview), иначе legacy-запись; в конце добавляем registry-only записи, которых нет в SIMS.
- `openSim(id)``base = id.split(':')[0]`; если `LabRegistry.has(base)``stopActive()`; `get(base).open({arg})`; `setActive`; иначе старый if-путь.
- `loadTheory(id)` — если `get(base).theory` есть → рендерим из него; иначе `THEORY[base]`.
- `closeSim()`/`_pauseAllSims()` — дополнительно `LabRegistry.stopActive()` / `destroyActive()`.
## RESUME STATE — Phase 4 done (2026-05-30, latest)
- Ф4: каталог симуляций в БД. Миграция `042_lab_sims.sql` (таблица lab_sims, сид 40), `backend/src/routes/lab.js` (GET /api/lab/sims auth; PATCH /:id + POST /reorder admin), mount в server.js, 11 тестов, переписан admin/sections/sims.js (убран хардкод ADMIN_SIMS).
- enabled зеркалится в legacy app_settings.sim_disabled_ids → lab.html без правок. preview-SVG остаются в коде.
- Ревью PASS (без блокеров). route-auth lint чистый. Миграция применена к живой БД. Запушено, remote синхронен.
- ВАЖНО: `npm test` = 3 PRE-EXISTING baseline-фейла (НЕ мои; документированы с lab-split). pre-commit хук: BASELINE_FAILS=3, блокирует только при >3. Мои 11 проходят, +0.
- ⚠️ Параллельная сессия коммитит в ветку — был 2 behind, rebase прошёл чисто (без пересечений по файлам). Всегда fetch+rebase перед push.
- НЕ ПРОВЕРЕНО В БРАУЗЕРЕ: админка «Симуляции» (грузит /api/lab/sims, тумблеры, звезда featured) + исчезновение выключенной симуляции на /lab.
- ОСТАЛОСЬ: Фаза 5 (курикулум: lab_sim_links + кнопки «Открыть в лаборатории» в учебнике/теории + связанная теория/задачи на странице sim). subject/grade/featured/tags уже в схеме lab_sims.
## RESUME STATE — Phase 3 done + FIXED (2026-05-30, ранее)
- HEAD=9069d80 (Ф3 + критический фикс). ЗАПУШЕНО, remote синхронен (0 0).
- ВАЖНЫЙ УРОК: коммит fc1139f был СЛОМАН — 2 edit'а (_register-all open-обёртка + lab-init Promise-обработка) не применились (упали по отступу old_string), а я запушил, не заметив. Ревью-агент поймал: lab.html убрал eager-скрипты, но open остался синхронным → ReferenceError на клике. Фикс в 9069d80. ПРАВИЛО: после каждого edit проверять `grep -c` маркера; не пушить пакет без поштучной верификации.
- ТЕПЕРЬ КОРРЕКТНО: open → LabLoader.ensure(id).then(rawOpen); openSim обрабатывает Promise. E2E vm-harness (click→ensure→load→rawOpen, pendulum/stereo:cube/molphys/alias magnetic) ALL PASS.
## RESUME STATE — Phase 3 done (исходный, до фикса)
- HEAD=70762be (Ф3). Ленивая загрузка кода: старт /lab ~530KB вместо ~2.9MB+600KB three.js (~6×).
- Новые файлы: `_loader.js`, `_sim_deps.js` (генерированный манифест). Правки: `_register-all.js`, `lab-init.js`, `lab.html` (eager сокращён до каркаса).
- Манифест SIM_DEPS: каждый sim → {open, files[], three}. Инвариант (проверен): файл, определяющий open-функцию, ВСЕГДА в files[] (кроме graph — он eager). Self-heal = страховка.
- ⚠️ PUSH: на момент завершения Ф3 окружение глючило (пустой вывод команд); локально 2 коммита ahead (Ф2-docs + Ф3), 0 behind. НУЖНО допушить: `git push origin feature/lab-content-engine` (мог не пройти из-за транзиентного auth — повторить).
- ⚠️ НЕ ПРОВЕРЕНО В БРАУЗЕРЕ (см. чеклист в phase-3-lazy-load.md handoff).
- ГЕНЕРАТОР МАНИФЕСТА был временным (%TEMP%, удалён). Логика: framework-set + статический анализ provides/refs (комментарии вырезаются) + транзитивное замыкание по ленивым файлам; 3D-set хардкод {crystal,orbitals,stereo,periodic}; EXTRA_DEPS={periodic:[_periodic_data.js]}. При Ф4/5 положить в tools/gen-sim-deps.js.
- СЛЕДУЮЩЕЕ: Ф4 (БД lab_sims + API + админка, backend — не трогает lab.html) или Ф5 (курикулум).
## RESUME STATE — Phase 2 done (2026-05-30, ранее)
- Ф2: 40 тел симуляций (~4420 строк) вынесены из lab.html (4880→484 строк) в `frontend/labs-bodies.html`. На месте региона — `#sim-bodies-host` + инлайн-скрипт с СИНХРОННЫМ XHR (`open(...,false)`), который во время парсинга грузит partial и `insertAdjacentHTML('beforebegin')` вставляет тела ДО хоста, затем удаляет хост. Тела присутствуют до DOMContentLoaded → обработчики geometry.js:3207 и порядок init сохранены.
- ctrl-бары (#ctrl-*) и #theory-panel ОСТАЛИСЬ в lab.html (они в topbar, не в регионе).
- partial раздаётся существующим `express.static(frontendDir)` (server.js:475) — новый роут не нужен.
- ГАРАНТИИ (механические, не браузерные): реконструкция before+region+after == оригинал ПОБАЙТОВО; id-мультимножество (newLabhost)+partial == оригинал; 40 sim-body div; node --check OK.
- ⚠️ НЕ ПРОВЕРЕНО В БРАУЗЕРЕ. РИСКИ к проверке вручную: (1) sync-XHR может блокироваться при file:// — но тут Express, ок; (2) консольное предупреждение о deprecated sync XHR — безвредно; (3) CSP на инлайн-скрипт — на странице уже есть инлайн-скрипты, должно быть ок; (4) кэш partial (?v=1) — при правках бампать версию.
- ПРОВЕРИТЬ: открыть /lab, дождаться каталога, открыть несколько симуляций (graph, pendulum, geometry — у неё DOMContentLoaded-кнопки, stereo:cube, opticsbench), убедиться что canvas рисуется и кнопки работают.
- ОТКАТ: `git revert <commit Ф2>` или вернуть регион из labs-bodies.html обратно.
- СЛЕДУЮЩЕЕ: Фаза 3 (ленивая загрузка кода) ИЛИ Фаза 4 (БД+админка, backend — без конфликтов с lab.html). Параллельная сессия всё ещё может править lab.html.
## RESUME STATE — Phase 1 done (2026-05-30, later)
- Коммиты: 36c091b → 0888a70 (фикс Ф0) → ebb2a9b (Ф1). HEAD=ebb2a9b, ЗАПУШЕНО, remote синхронен.
- Ф1: централизованный `_register-all.js` (data-driven из SIMS+THEORY+OPEN map 40 шт), if-цепочка openSim удалена, _pilots.js удалён, LAB_SIM_ALIASES добавлены. Ревью PASS, vm-harness ALL PASS.
- SIMS/THEORY/_pauseAllSims/closeSim/ALL_SIM_BODIES/ALL_CTRL_BARS НАМЕРЕННО оставлены (источники данных + lifecycle-дробовик) — паритет без браузера. Их удаление = Фаза 2 (с ленивым mount).
- БЛОКЕР-РИСК: параллельная сессия (biochem/opticsbench) активно правит lab.html и откатывала мои include-правки. Перед Фазой 2 (вынос ~3000 строк из lab.html) — СОГЛАСОВАТЬ, иначе конфликты/потеря работы.
- СЛЕДУЮЩЕЕ: пользователю желательно открыть /lab в браузере и кликнуть несколько симуляций (особенно с :arg — стерео-фигуры, оптика-режимы) перед Фазой 2.
## RESUME STATE (2026-05-30, ранее)
- Ветка `feature/lab-content-engine`. Коммиты: 36c091b (Фаза 0, была неполной) → 8f72d68 (фикс 3 блокеров, ЗАПУШЕН).
- Первое ревью Фазы 0 = FAIL (3 блокера: _registry.js не подключён, пилоты не зарегистрированы, loadTheory не адаптирован). ВСЕ ТРИ ИСПРАВЛЕНЫ в 8f72d68:
- `_registry.js` подключён в lab.html:4799 (после three.js).
- `frontend/js/labs/_pilots.js` — регистрирует graph/quadratic/pendulum; подключён в lab.html:4863 (defer, последним).
- `loadTheory` (lab-glue.js:951) — реестр в приоритете.
- СЛЕДУЮЩИЙ ШАГ: повторное независимое ревью Фазы 0 (агент). Если PASS → Фаза 1.
- Фаза 1 решено вести ПО КАТЕГОРИЯМ с коммитами (math→phys→chem→bio→game), legacy удалять в конце.
- НЕ ПРОВЕРЕНО в браузере (нет автоматизации). Паритет — статически + ревью.
- ВАЖНО: _open* функции глобальны (graph.js:506, quadratic.js:454, pendulum.js:1749). P_* — top-level const в lab-glue.js (доступны cross-script). THEORY — const в lab-init.js. Манифест Фазы1 регистрировать в самом sim-файле, но следить за порядком (preview/theory как fn если файл грузится до lab-glue/lab-init).
- УРОК: lab-glue.js/lab-init.js часто перенумеровываются линтером — перед Edit перечитывать. Делать правки ПО ОДНОЙ с проверкой (был сбой пакетного выполнения).
## Temporary Workarounds
- Пилоты (graph/quadratic/pendulum) оставлены в SIMS/THEORY для порядка карточек и единого источника теории; merge перекрывает по id. Удалить в Фазе 1.
## Known follow-ups (из ревью Фазы 0)
- При переключении на LEGACY-симуляцию `LabRegistry._active` не очищается → лишний destroyActive() на неактивной. Безвредно сейчас; очистить `_active` на legacy-open в Фазе 1.
## Cross-Phase Dependencies
- Фаза 1 опирается на ядро реестра из Фазы 0.
- Фаза 3 (ленивая загрузка) опирается на манифесты с зависимостями движков (Фаза 1/2).
- Фаза 4 (БД) мёржит код-манифесты Фазы 1 с оверрайдами.
- Фаза 5 использует поля subject/grade/topics из манифестов.
## Deep-links (сохранить!)
`openSim('stereo:figure')`, `?stereofig=`, обратная совместимость `magnetic/coulomb→emfield`, `thinlens/mirrors/refraction→opticsbench`.
## Проектные правила (НЕ нарушать)
- Иконки: только inline SVG `.ic`, НЕ эмоджи.
- Поиск по коду: ast-index, НЕ Grep tool.
- БД: встроенный `node:sqlite` DatabaseSync, НЕ better-sqlite3.
- Git: коммитить только изменённые файлы.