c1c5bafaff
- Миграция 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>
117 lines
15 KiB
Markdown
117 lines
15 KiB
Markdown
# 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-мультимножество (newLab−host)+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: коммитить только изменённые файлы.
|