chore(sim-builder): план фичи (8 фаз) — конструктор симуляций

This commit is contained in:
Maxim Dolgolyov
2026-06-13 10:54:45 +03:00
parent c4ca8bcae7
commit d1d52d806d
10 changed files with 469 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
# Feature Context: Конструктор симуляций (SimForge)
## Current State
- Фаза 0 не начата. Ветка `feature/sim-builder` от `master`.
- Лаборатория уже декларативна на уровне регистрации: `frontend/js/labs/_registry.js`
(`LabRegistry.register/get/all/setActive/stop/destroy/resolvePreview`), манифест с
`open(ctx)/mount(host)/stop/destroy`. ~40 симуляций — рукописные JS-модули в `frontend/js/labs/`.
- Каталог в БД: миграция `042_lab_sims.sql` (`lab_sims`), роуты `backend/src/routes/lab.js`
(`GET /api/lab/sims`, PATCH/:id, POST /reorder, links). Привязка к программе: `043_lab_sim_links.sql`.
## Архитектурные решения (зафиксированы при планировании)
- **Спека = JSON-данные.** Версия `specVersion`. Корень: `{ specVersion, meta, viewport, params[], objects[], physics?, plots[], controls }`.
- **Движок выражений безопасный** — собственный парсер (расширение `y=f(x)` из graph.js):
токенайзер → AST → eval по окружению `{ params, t, объекты, whitelisted Math fns }`.
⛔ Без `eval`/`Function`. Whitelist: + - * / ^ %, sin cos tan asin acos atan sqrt abs exp ln log min max floor ceil round sign pi e, сравнения, ?:.
- **Рантайм** `window.SimEngine.mount(host, spec) -> instance{ play, pause, reset, setParam, destroy }`.
Рендер: canvas для геометрии/трасс + SVG/absolute-div оверлей для подписей (KaTeX).
Регистрируется в LabRegistry адаптером (одна функция строит манифест из спеки).
- **Объект**: `{ id, type, ...props-with-bindings }`. type ∈ point|segment|vector|circle|rect|polyline|path|label|image. Любое числовое свойство может быть числом ИЛИ строкой-выражением.
- **Физический режим (Фаза 2)**: объект с `body:{ mass, vx, vy, fixed }` интегрируется `_fx_motion`; силы `physics:{ gravity, springs[], collisions, friction, walls }`. Формульный и физический режимы сосуществуют (формульные объекты — кинематические).
- **Безопасность шаринга**: published-спека валидируется на сервере (размер, схема, глубина AST, число объектов/параметров); подписи-строки санитизируются как svg/текст.
## Temporary Workarounds
- (нет)
## Cross-Phase Dependencies
- Ф1 (графики/drag) зависит от рантайма Ф0.
- Ф2 (физика) зависит от Ф0 (модель объектов/цикл).
- Ф4 (билдер) зависит от Ф0–Ф2 (что строить) + Ф3 (куда сохранять).
- Ф5 (каталог) зависит от Ф3 (БД) + Ф0 (адаптер LabRegistry).
- Ф6 (раздача) зависит от Ф3+Ф5.
- Ф7 (доска) зависит от Ф0 (рантайм) + Ф5 (источник sim) + существующего `simOpen/simState`.
## Implementation Notes
- Каждая фаза должна оставлять /lab рабочим (Incremental).
- Тестировать рантайм Ф0–Ф2 рукописными спеками-фикстурами (без билдера).
- Reuse > переписывание: сначала смотреть `_fx_motion`, `_graph_panel`, `graph.js`.
## RESUME STATE
- Последний коммит фичи: — (ещё нет)
- Текущая фаза: Phase 0 — Runtime core (Not Started)
- Режим: Automated / Orchestrator / Incremental
+68
View File
@@ -0,0 +1,68 @@
# Feature: Конструктор симуляций (SimForge)
**Branch:** `feature/sim-builder`
**Base branch:** `master`
**Created:** 2026-06-13
**Status:** 🟡 In Progress
**Strategy:** Incremental
**Mode:** Automated
**Execution:** Orchestrator
## Summary
Полноценный движок авторинга интерактивных 2D-симуляций для учителя-непрограммиста.
Учитель собирает симуляцию из **данных** (JSON-спека): параметры-слайдеры, объекты
(фигуры/векторы/точки/подписи с LaTeX), привязанные **формулами** к параметрам и времени
`t`; настоящая физика (гравитация/пружины/столкновения/трение); графики; перетаскивание.
Сохраняет в БД, публикует в каталог лаборатории, раздаёт классу, открывает на доске
онлайн-урока, клонирует чужие и стартует из шаблонов.
Спека — это **данные, не код**. Движок выражений — безопасный (whitelisted-математика),
⛔ без `eval`/`Function`/доступа к DOM/глобалам: спека шарится между людьми.
## Build & Test Commands
- **Build:** нет (vanilla JS, без бандлера)
- **Test:** `npm test``backend/`, `node --test tests/*.test.js`)
- **Lint:** `npm run lint:routes``backend/`)
- ⚠️ После роутов/миграций: `npm run migrate` (живая БД) + рестарт сервера (авто-перезагрузки нет).
- ⚠️ `npm test` имеет baseline 3 pre-existing fail (auth.test.js) — хук толерантен (BASELINE_FAILS=3).
## Project Constraints (соблюдают ВСЕ агенты)
- ⛔ Никаких эмодзи в коде — только inline SVG `.ic`.
- Поиск по коду: `ast-index` (символы/usages/callers) + `vex` (semantic). НЕ Grep tool.
- БД — встроенный `node:sqlite` (`DatabaseSync`), НЕ better-sqlite3. Живая БД `backend/data/learnspace.db`.
- Frontend — vanilla JS, `window.LS.*` (js/api.js), без бандлера. Статика через Express.
- Стейджить файлы поимённо (НЕ `git add -A` — в репо много мусорных untracked).
- Движок выражений — безопасный парсер, не `eval`/`new Function`.
- Переиспользовать: LabRegistry, `_fx_motion`, `_graph_panel`, `_phys_visuals`, `_util`,
парсер `y=f(x)` из graph.js; паттерн раздачи из «Мои материалы»; `lab_sim_links`;
конвейер встраивания sim на доску (`simOpen/simState/simMode/simAnnotate`).
## Phases
- [ ] Phase 0: Спека v1 + рантайм (формульные сцены) [domain: frontend] → [subplan](./phase-0-runtime-core.md)
- [ ] Phase 1: Графики + интеракции [domain: frontend] → [subplan](./phase-1-plots-interactions.md)
- [ ] Phase 2: Физический интегратор [domain: frontend] → [subplan](./phase-2-physics.md)
- [ ] Phase 3: БД + API (custom_sims) [domain: backend] → [subplan](./phase-3-persistence-api.md)
- [ ] Phase 4: Билдер (редактор) [domain: frontend] → [subplan](./phase-4-builder-ui.md)
- [ ] Phase 5: Каталог (custom-sims в /lab) [domain: fullstack] → [subplan](./phase-5-catalog.md)
- [ ] Phase 6: Раздача / шаблоны / клон / программа [domain: fullstack] → [subplan](./phase-6-sharing.md)
- [ ] Phase 7: Доска онлайн-урока [domain: fullstack] → [subplan](./phase-7-classroom.md)
## Phase Progress Log
| Phase | Domain | Status | Review | Build | Committed |
|-------|--------|--------|--------|-------|-----------|
| Phase 0: Runtime core | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 1: Plots & interactions | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 2: Physics | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 3: Persistence + API | backend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 4: Builder UI | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 5: Catalog | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 6: Sharing | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 7: Classroom | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
## Final Review
- [ ] Comprehensive code review (final-reviewer)
- [ ] Security review (safe expression eval, ownership, sanitization)
- [ ] Full test suite passes (within baseline)
- [ ] Merged to `master`
+48
View File
@@ -0,0 +1,48 @@
# Phase 0: Спека v1 + рантайм (формульные сцены)
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
## Objective
Заложить ядро: формат JSON-спеки v1, безопасный движок выражений, рантайм `SimEngine`,
адаптер регистрации в `LabRegistry`. После фазы рукописная спека «брошенное тело» играет в /lab.
## Tasks
- [ ] Задокументировать формат спеки v1 в шапке нового файла + в CONTEXT.md (params, objects, viewport, controls).
- [ ] `frontend/js/labs/_sim_expr.js` — безопасный движок выражений: токенайзер → AST → `evaluate(ast, env)`. Whitelist математики (см. CONTEXT.md). Парсер расширяет логику `y=f(x)` из `graph.js` (посмотреть и переиспользовать, не дублировать). ⛔ без `eval`/`Function`. `compile(src) -> {fn(env), error}`.
- [ ] `frontend/js/labs/_sim_engine.js``window.SimEngine.mount(host, spec)`:
- создаёт canvas (мир→экран трансформация по `viewport`) + оверлей-слой для подписей (KaTeX через существующий путь рендера формул);
- объекты: point|segment|vector|circle|rect|polyline|path|label (числовые свойства = число или строка-выражение, компилируются один раз);
- игровой цикл `requestAnimationFrame`: пересчёт `t`, перевычисление привязок, перерисовка;
- контролы: слайдеры-параметры (из `params[]`), кнопки play/pause/reset; API инстанса `{ play, pause, reset, setParam, destroy }`.
- [ ] `frontend/js/labs/_sim_adapter.js``registerSpecSim(spec)` строит манифест LabRegistry (`open(ctx)``SimEngine.mount`, `stop/destroy`, `preview` из спеки) и регистрирует.
- [ ] Фикстура-демо: рукописная спека «projectile» (угол/скорость слайдеры, точка x=v·cosθ·t, y=v·sinθ·t−5t²) — зарегистрировать как `customdemo` для проверки в /lab (за временным флагом/разделом, не светить ученикам).
- [ ] Подключить новые файлы в /lab (lazy через существующий `_loader`/`_sim_deps` или прямыми тегами — выбрать минимально-инвазивно, не ломая старт).
## Files to Modify/Create
- `frontend/js/labs/_sim_expr.js` — движок выражений (new)
- `frontend/js/labs/_sim_engine.js` — рантайм (new)
- `frontend/js/labs/_sim_adapter.js` — адаптер LabRegistry (new)
- `frontend/js/labs/_sim_demo.js` — демо-спека-фикстура (new, временная)
- `frontend/lab.html` или `_sim_deps.js` — подключение файлов (минимальная правка)
## Acceptance Criteria
- В /lab открывается демо-симуляция, слайдеры меняют движение, play/pause/reset работают.
- Движок выражений не использует eval/Function; некорректная формула не роняет рантайм (показывает ошибку/0).
- Существующие ~40 симуляций и старт /lab не сломаны.
## Notes
- Подписи с LaTeX — переиспользовать существующий рендер формул (KaTeX), не тянуть новый.
- Мир-координаты с осью Y вверх (математические), трансформация в экранные внутри движка.
- Производительность: компилировать выражения один раз при mount, в цикле только evaluate.
## Review Checklist
- [ ] Все задачи выполнены
- [ ] Нет eval/new Function в движке выражений
- [ ] Нет эмодзи (только SVG .ic)
- [ ] Старт /lab и существующие симуляции не регрессировали
- [ ] Код в стиле проекта (vanilla, IIFE-модули labs/)
## Handoff to Next Phase
<!-- Заполняет реализатор -->
@@ -0,0 +1,40 @@
# Phase 1: Графики + интеракции
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
## Objective
Добавить в рантайм графики (plot-объекты), перетаскиваемые ручки (drag → параметр),
векторы и числовые readout. После фазы спека со слайдером, draggable-точкой и live-графиком работает.
## Tasks
- [ ] Plot-объект в спеке: `{ type:'plot', expr:'...', var:'x', range:[a,b], samples, trace? }`
рисует график выражения; `trace:true` — накапливает след по `t`. Переиспользовать `_graph_panel.js` (посмотреть API).
- [ ] Draggable-ручка: объект/маркер с `drag:{ param:'<name>', axis:'x|y|xy', min,max }` — перетаскивание мышью/тачем меняет параметр (и наоборот, позиция следует за параметром). Хит-тест в мир-координатах.
- [ ] Readout: `{ type:'readout', label, expr, unit, precision }` — живое значение выражения как текст/бейдж.
- [ ] Vector-объект с привязкой к (origin, dx, dy)-выражениям + стрелка.
- [ ] Тач-поддержка drag (pointer events), не ломая существующую логику доски/лабы.
- [ ] Обновить демо-спеку: добавить слайдер, draggable-точку, plot + readout.
## Files to Modify/Create
- `frontend/js/labs/_sim_engine.js` — типы plot/readout/vector, drag-интеракции (modify)
- `frontend/js/labs/_sim_demo.js` — расширить демо (modify)
## Acceptance Criteria
- Перетаскивание ручки меняет параметр; зависимые объекты/график обновляются.
- График строится по выражению; trace накапливает след во времени.
- Readout показывает живое значение. Тач работает.
## Notes
- Drag не должен конфликтовать с pan/zoom рантайма (если есть). Приоритет хит-теста — ручки.
- Сэмплинг графика разумный (без фриза на больших range).
## Review Checklist
- [ ] Все задачи выполнены
- [ ] Drag работает мышью и тачем
- [ ] Нет регрессий рантайма Ф0
- [ ] Нет эмодзи, стиль проекта
## Handoff to Next Phase
<!-- Заполняет реализатор -->
+42
View File
@@ -0,0 +1,42 @@
# Phase 2: Физический интегратор
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
## Objective
Добавить настоящую физику: тела с массой, гравитация/пружины/столкновения/трение,
перетаскивание тел силой, траектории. Динамика считается движком, а не формулой.
После фазы маятник/столкновения/брошенное тело идут динамически из спеки.
## Tasks
- [ ] Блок `physics` в спеке: `{ enabled, gravity:{x,y}, friction, walls:[...], restitution }`.
- [ ] Тело-объект: `body:{ mass, vx, vy, fixed }` — интегрируется (опора на `_fx_motion.js`, посмотреть API; не дублировать интегратор).
- [ ] Пружины: `springs:[{ a, b, k, length }]` (между телами или телом и точкой-якорем).
- [ ] Столкновения: упругие шары/стены (restitution), базовый бродфейз достаточно (N небольшое).
- [ ] Drag тела: перетаскивание задаёт позицию/скорость (отпустил — летит). Кинематические (формульные) объекты Ф0 сосуществуют с физическими.
- [ ] Траектория: накопление следа центра тела (toggle в спеке).
- [ ] Демо-спеки: «маятник» (груз+нить как пружина/констрейнт), «упругие шары».
## Files to Modify/Create
- `frontend/js/labs/_sim_engine.js` — физический режим, интеграция с _fx_motion (modify)
- `frontend/js/labs/_sim_physics.js` — обёртка интегратора/коллизий, если чище отдельно (new, опц.)
- `frontend/js/labs/_sim_demo.js` — физ-демо (modify)
## Acceptance Criteria
- Тело под гравитацией падает/летит по параболе через интегратор (не по формуле).
- Пружина колеблет груз; шары упруго сталкиваются; стены отражают.
- Drag тела работает; формульные объекты Ф0 продолжают работать в той же сцене.
## Notes
- Шаг интегратора фиксированный (накопитель dt) для стабильности.
- Не переусложнять коллизии — школьный уровень (круги/стены).
## Review Checklist
- [ ] Все задачи выполнены
- [ ] Использует _fx_motion, без своего дубля интегратора без причины
- [ ] Стабильность (нет взрыва энергии на разумных параметрах)
- [ ] Нет регрессий Ф0/Ф1
## Handoff to Next Phase
<!-- Заполняет реализатор -->
@@ -0,0 +1,51 @@
# Phase 3: БД + API (custom_sims)
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Сохранение custom-симуляций: таблица БД, CRUD API под авторизацией с проверкой владения,
серверная валидация спеки. После фазы спека сохраняется/грузится/удаляется через API.
## Tasks
- [ ] Миграция `backend/src/db/migrations/0NN_custom_sims.sql` (следующий свободный номер):
таблица `custom_sims` (id, owner_id FK users ON DELETE CASCADE, title, description, subject,
grade, cat, spec_json TEXT, status TEXT 'draft|published' DEFAULT 'draft', version INT,
created_at, updated_at) + индекс по owner_id, status.
- [ ] Контроллер `backend/src/controllers/customSimController.js`: list (own + published), get,
create, update, remove. Владение проверяется на mutate (owner или admin).
- [ ] Серверная валидация спеки `validateSpec(spec)`: размер JSON, `specVersion`, лимиты
(число params/objects, глубина строк-выражений), типы объектов из whitelist, строки-подписи
обрезаются/санитизируются. Отказ с 400 при нарушении. ⛔ Никакого исполнения спеки на сервере.
- [ ] Роуты `backend/src/routes/customSims.js`: `GET /api/custom-sims` (свои+published),
`GET /api/custom-sims/:id`, `POST /` (teacher/admin), `PUT /:id`, `DELETE /:id`. Смонтировать в server.js под authMiddleware + фича-гейт при необходимости.
- [ ] Клиент `js/api.js`: `customSimsList/Get/Create/Update/Delete` + добавить в `window.LS`.
- [ ] Тесты `backend/tests/custom-sims.test.js`: CRUD, ownership (403 чужой), валидация (400 кривая спека). Использовать `seedRow()` паттерн (устойчив к дрейфу схемы).
## Files to Modify/Create
- `backend/src/db/migrations/0NN_custom_sims.sql` (new)
- `backend/src/controllers/customSimController.js` (new)
- `backend/src/routes/customSims.js` (new)
- `backend/src/server.js` — монтирование роутера (modify)
- `js/api.js` — клиентские методы (modify)
- `backend/tests/custom-sims.test.js` (new)
## Acceptance Criteria
- POST сохраняет спеку, GET возвращает свои+published, PUT/DELETE только владельцу/админу (403 иначе).
- Кривая/огромная спека → 400. Тесты зелёные (в пределах baseline).
- `npm run migrate` применяет таблицу; роут не отдаёт SPA-fallback после рестарта.
## Notes
- node:sqlite (DatabaseSync), НЕ better-sqlite3.
- Спека хранится как TEXT(JSON); парс/валидация — на входе.
- Не делать blanket `router.use(requireRole('admin'))` — read-роуты auth-only, мутации — inline requireRole.
## Review Checklist
- [ ] Все задачи выполнены
- [ ] Ownership и валидация спеки покрыты тестами
- [ ] Миграция идемпотентна (IF NOT EXISTS / INSERT OR IGNORE где надо)
- [ ] `npm run lint:routes` чисто; тесты в пределах baseline
## Handoff to Next Phase
<!-- Заполняет реализатор -->
+46
View File
@@ -0,0 +1,46 @@
# Phase 4: Билдер (редактор)
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
## Objective
Учительский редактор: страница с живым превью и панелями для сборки спеки без кода.
После фазы учитель собирает рабочую симуляцию с нуля в UI и сохраняет в БД (Ф3).
## Tasks
- [ ] Страница `frontend/sim-builder.html` (доступ teacher/admin; сайдбар как у других страниц).
- [ ] Левая/правая панель + центр-превью (встроенный `SimEngine` инстанс, перемонтаж при правках).
- [ ] Панель **Параметры**: добавить/удалить параметр (имя, min, max, step, начальное, единица) → слайдер в превью.
- [ ] Панель **Объекты**: добавить объект (тип из whitelist), редактор свойств; числовые поля
принимают число ИЛИ выражение; палитра-помощник функций/параметров; подпись с LaTeX.
- [ ] Панель **Графики/Физика**: добавить plot (expr/var/range/trace); тумблер физики + её поля (Ф2).
- [ ] Размещение объектов мышью на превью (клик-поставить, drag-двигать) с синхроном в свойства.
- [ ] Мета: заголовок, описание, предмет, класс, категория, превью-картинка (опц.).
- [ ] Save/Load через `LS.customSims*` (Ф3): новый / редактировать существующий; кнопка «Тест» (play inline).
- [ ] Валидация на клиенте (понятные ошибки до сохранения) + показ ошибок выражений.
## Files to Modify/Create
- `frontend/sim-builder.html` (new) — страница + инлайн-логика редактора
- `frontend/js/labs/_sim_engine.js` — при необходимости hook'и для билдера (live re-mount, highlight) (modify, минимально)
- `js/api.js` — если нужны доп. методы (modify, опц.)
- ссылка в сайдбаре/навигации для учителя (modify соответствующего include)
## Acceptance Criteria
- Учитель с нуля добавляет параметры/объекты/график, видит живое превью, сохраняет, открывает заново и видит то же.
- Ошибка в выражении показывается понятно, не роняет редактор.
- Нет эмодзи, дизайн в системе ls.css.
## Notes
- Прагматично: форма-панели + лёгкий drag на превью. НЕ полноценный node-граф.
- Это frontend-фаза — использовать гайдлайны frontend-design.
- Большой файл — держать логику модульной (можно вынести в `frontend/js/sim-builder.js`).
## Review Checklist
- [ ] Все задачи выполнены
- [ ] Полный цикл build→save→reload→edit работает
- [ ] Доступ только teacher/admin
- [ ] Нет эмодзи; дизайн-система соблюдена
## Handoff to Next Phase
<!-- Заполняет реализатор -->
+42
View File
@@ -0,0 +1,42 @@
# Phase 5: Каталог (custom-sims в /lab)
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** fullstack
## Objective
Сохранённые custom-симуляции появляются и играют в /lab наравне со встроенными;
раздел «Мои симуляции», редактирование/удаление из каталога, deep-link.
## Tasks
- [ ] При загрузке /lab подтягивать custom-sims (свои + published) из `GET /api/custom-sims`
и регистрировать через `registerSpecSim` (Ф0-адаптер) с id `custom:<dbid>`.
- [ ] Карточки в каталоге: категория/предмет/класс из меты; бейдж «Моя»/«Опубликована».
- [ ] Раздел/фильтр «Мои симуляции» в /lab.
- [ ] Кнопки на карточке custom-sim: «Редактировать» → `sim-builder.html?id=<id>`, «Удалить» (владельцу).
- [ ] Deep-link `/lab?sim=custom:<id>` открывает напрямую (расширить существующий `LAB_SIM_ALIASES`/openSim).
- [ ] Ленивая загрузка движка (`_sim_*.js`) — только когда открыта custom-sim (через `_loader`/`_sim_deps`).
## Files to Modify/Create
- `frontend/js/labs/lab-glue.js` и/или `lab-init.js` — загрузка+регистрация custom-sims, карточки, фильтр (modify)
- `frontend/js/labs/_sim_deps.js``_sim_*.js` в ленивые зависимости (modify)
- `js/api.js` — при необходимости (modify, опц.)
## Acceptance Criteria
- Сохранённая в Ф4 симуляция видна в /lab, открывается и играет.
- «Мои симуляции» показывает свои (вкл. draft); published видят и другие.
- Edit/Delete с карточки работают; deep-link открывает.
- Старт /lab не тормозит (движок грузится лениво).
## Notes
- НЕ ломать существующий каталог встроенных (lab_sims) — custom-список добавляется поверх.
- id-неймспейс `custom:` чтобы не конфликтовать со встроенными.
## Review Checklist
- [ ] Все задачи выполнены
- [ ] Встроенные симуляции и старт /lab не регрессировали
- [ ] Draft видит только владелец; published — все
- [ ] Ленивая загрузка движка работает
## Handoff to Next Phase
<!-- Заполняет реализатор -->
+47
View File
@@ -0,0 +1,47 @@
# Phase 6: Раздача / шаблоны / клон / программа
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** fullstack
## Objective
Экосистема вокруг custom-sims: публикация, раздача классу, клонирование чужих,
старт из шаблонов, привязка к программе (учебник/тема).
## Tasks
- [ ] Публикация: тумблер draft↔published в билдере/каталоге (PUT status). Только владелец/админ.
- [ ] Раздача классу: `POST /api/custom-sims/:id/share { classId }` — по паттерну «Мои материалы»
(`shareMaterial`): ученики класса получают доступ/уведомление. Решить — ссылка-доступ или копия
(рекоменд.: доступ-ссылка на published + запись в lab_sim_links/доступ; копия избыточна).
- [ ] Клон: `POST /api/custom-sims/:id/clone` — копия спеки новому владельцу (draft). Кнопка «Клонировать» на чужой published-карточке.
- [ ] Шаблоны: набор стартовых спек (встроенные JSON-фикстуры: пустая, маятник, график, бросок) →
«Создать из шаблона» в билдере; «Создать из существующей» = clone.
- [ ] Привязка к программе: переиспользовать `lab_sim_links` (kind=textbook|topic) для `custom:<id>`;
чип «Связано с программой» (как у встроенных, `_loadRelated`) и кнопка «В лабораторию» с карточки учебника.
- [ ] Тесты: share (доступ ученику), clone (новый владелец, draft), ownership на публикации.
## Files to Modify/Create
- `backend/src/controllers/customSimController.js` — share/clone/publish (modify)
- `backend/src/routes/customSims.js` — роуты share/clone (modify)
- `backend/src/controllers/.../lab links` — связи для custom (reuse `lab.js` links, modify при необходимости)
- `frontend/sim-builder.html` / `frontend/js/labs/lab-glue.js` — шаблоны, кнопки публикации/клона/раздачи (modify)
- `js/api.js` — методы share/clone (modify)
- `backend/tests/custom-sims-share.test.js` (new)
## Acceptance Criteria
- Учитель публикует; другой учитель видит и клонирует к себе (draft).
- Выданная классу симуляция доступна ученикам класса (с уведомлением).
- Старт из шаблона создаёт рабочую заготовку. Привязка к учебнику показывает чип/кнопку.
## Notes
- Раздача — переиспользовать существующий механизм доступа/уведомлений, не строить новый.
- Решение копия-vs-ссылка зафиксировать в CONTEXT.md.
## Review Checklist
- [ ] Все задачи выполнены
- [ ] Ownership на publish/share/clone покрыт тестами
- [ ] Ученик класса получает доступ; чужой — нет
- [ ] Reuse материалов/доступа/links, без дублей
## Handoff to Next Phase
<!-- Заполняет реализатор -->
+43
View File
@@ -0,0 +1,43 @@
# Phase 7: Доска онлайн-урока
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** fullstack
## Objective
Открывать custom-симуляцию на доске онлайн-урока через существующий конвейер встраивания
sim, с синхроном параметров классу и аннотациями поверх.
## Tasks
- [ ] Учитель в classroom выбирает sim для доски: добавить в список источников свои+published custom-sims (рядом со встроенными).
- [ ] Открытие на доске через существующий `simOpen` (controller `classroom/sim.js`, роут `/:id/sim`) —
для custom передаётся `custom:<id>`; рантайм `SimEngine` монтируется в sim-контейнер доски.
- [ ] Синхрон состояния: параметры/play-pause/время транслировать классу через `simState/simMode`
(как для встроенных) — ученики видят те же значения слайдеров и фазу анимации.
- [ ] Аннотации поверх — через существующий `simAnnotate` (без изменений конвейера).
- [ ] Закрытие/смена sim корректно размонтирует `SimEngine` (destroy).
- [ ] Тест/проверка: открыть custom-sim на доске, подвигать слайдер у учителя → у ученика синхрон.
## Files to Modify/Create
- `frontend/classroom.html` — выбор custom-sim в источниках доски, монтаж SimEngine, проброс состояния (modify)
- `backend/src/controllers/classroom/sim.js` — поддержать `custom:<id>` (валидация доступа к published/own) (modify)
- `js/api.js` — при необходимости (modify, опц.)
## Acceptance Criteria
- Учитель открывает custom-sim на доске; ученики видят её синхронно (параметры/анимация/режим).
- Аннотации поверх работают; закрытие чистит ресурсы.
- Существующее встраивание встроенных sim не регрессировало.
## Notes
- Reuse simOpen/simState/simMode/simAnnotate — НЕ строить новый канал синхрона.
- Доступ: на доску можно класть только свои или published (проверка на сервере).
- classroom.html большой (8240 строк) — править точечно.
## Review Checklist
- [ ] Все задачи выполнены
- [ ] Синхрон параметров учитель→ученики работает
- [ ] Доступ к custom-sim на доске проверяется
- [ ] Встроенные sim на доске не сломаны; SimEngine корректно размонтируется
## Handoff to Next Phase
<!-- Финальная фаза -->