feat(sim-builder): фаза 7 — custom-sim на доске онлайн-урока (синхрон параметров классу, аннотации)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Phase 7: Доска онлайн-урока
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Status:** ✅ Implemented (pending commit)
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** fullstack
|
||||
|
||||
@@ -9,14 +9,21 @@
|
||||
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 на доске, подвигать слайдер у учителя → у ученика синхрон.
|
||||
- [x] Учитель в classroom выбирает sim для доски: добавить в список источников свои+published custom-sims (рядом со встроенными).
|
||||
→ `_crLoadCustomSims()` (`LS.customSimsList`) + мёрж в `_crRenderSimGrid`; карточка с бейджем «Моя», id=`custom:<dbid>`.
|
||||
- [x] Открытие на доске через существующий `simOpen` (controller `classroom/sim.js`, роут `/:id/sim`) —
|
||||
для custom передаётся `custom:<id>`. Рантайм НЕ монтируется напрямую в доску: доска уже грузит sim
|
||||
в **iframe** `/lab?embed=1&sim=...`; для custom это `/lab?embed=1&sim=custom:<id>`, где `LabCustom`/
|
||||
`registerSpecSim` монтирует `SimEngine` (путь Ф5). Конвейер iframe переиспользован 1:1.
|
||||
- [x] Синхрон состояния: параметры + play/pause транслируются классу через существующий мост
|
||||
`sim_state`/`apply_sim_state` (lab-glue) → `simState`. Custom-sim подключён к мосту через
|
||||
`_bridgeCustomSimState(real)` (getState/applyState поверх `SimEngine.params`/`setParam`/`play`/`pause`).
|
||||
⚠️ **Время t (фаза анимации) жёстко НЕ синхронится** — только параметры слайдеров + признак running.
|
||||
- [x] Аннотации поверх — через существующий `simAnnotate` (без изменений конвейера, id-agnostic).
|
||||
- [x] Закрытие/смена sim: `onSimClose` сбрасывает `frame.src='about:blank'` → весь документ iframe
|
||||
(SimEngine + rAF + слушатели + реестр состояния) сносится вместе с iframe. Утечек нет.
|
||||
- [x] Проверка: логическая (трасса teacher→backend→SSE→student) + node --check + npm test (в baseline).
|
||||
Headless classroom тяжёл; синхрон проверен по конвейеру, открытие/доступ/закрытие — по коду.
|
||||
|
||||
## Files to Modify/Create
|
||||
- `frontend/classroom.html` — выбор custom-sim в источниках доски, монтаж SimEngine, проброс состояния (modify)
|
||||
@@ -34,10 +41,41 @@ sim, с синхроном параметров классу и аннотаци
|
||||
- classroom.html большой (8240 строк) — править точечно.
|
||||
|
||||
## Review Checklist
|
||||
- [ ] Все задачи выполнены
|
||||
- [ ] Синхрон параметров учитель→ученики работает
|
||||
- [ ] Доступ к custom-sim на доске проверяется
|
||||
- [ ] Встроенные sim на доске не сломаны; SimEngine корректно размонтируется
|
||||
- [x] Все задачи выполнены
|
||||
- [x] Синхрон параметров учитель→ученики работает (sim_state мост, params+running)
|
||||
- [x] Доступ к custom-sim на доске проверяется (server simOpen: own|published|admin; иначе 403/404)
|
||||
- [x] Встроенные sim на доске не сломаны (id-ветка для встроенных без изменений); ресурсы чистятся через about:blank
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Финальная фаза -->
|
||||
|
||||
## Handoff — итог Ф7 (степень синхрона, что осталось)
|
||||
|
||||
**Изменённые файлы (минимально, аддитивно):**
|
||||
- `backend/src/controllers/classroom/sim.js` (+21/-2): `simOpen` принимает `custom:<dbid>`,
|
||||
валидирует доступ (владелец ИЛИ published ИЛИ admin → иначе 404/403). Встроенный id — старый regex.
|
||||
`simState/simMode/simAnnotate/simClose` НЕ менялись (state-объект и так произвольный, ≤64KB).
|
||||
- `frontend/classroom.html` (+31/-4): `_crLoadCustomSims()` (кэш `LS.customSimsList`),
|
||||
`crOpenSimPicker` async + предзагрузка, `_crRenderSimGrid` мёржит custom (бейдж «Моя», id `custom:<dbid>`,
|
||||
XSS-escape заголовков/id в onclick). `crPickSim` уже передаёт id как есть.
|
||||
- `frontend/js/labs/lab-glue.js` (+48/-1): `_bridgeCustomSimState(real)` — мост состояния для custom-sim;
|
||||
вызывается в `_registerLazy.open()` после `real.open(ctx)` (только embed). Регистрирует
|
||||
`getState={params,running}` / `applyState` (setParam+play/pause) под ключом `_autoSim`
|
||||
(`custom:<dbid>`), запускает `_startStateEmit`. Тем же `_simStateRegistry`/каналом, что встроенные.
|
||||
|
||||
**Степень синхрона:** параметры слайдеров + признак воспроизведения (play/pause) — ПОЛНЫЙ синхрон
|
||||
учитель→ученики (demo-режим). Время `t` (фаза анимации) НЕ синхронизируется покадрово: ученик
|
||||
запускает свой rAF при running=true, фаза может разъезжаться. Достаточно по требованиям фазы.
|
||||
При желании в будущем: добавить `t` в state и `inst._t` сеттер (потребует расширения SimEngine API).
|
||||
|
||||
**Открытие на доске:** через iframe `/lab?embed=1&sim=custom:<id>` (НЕ прямой mount в доску) —
|
||||
переиспользован существующий конвейер; SimEngine монтируется уже внутри iframe (путь Ф5).
|
||||
|
||||
**Доступ:** двойная проверка — (1) `simOpen` на сервере при постановке на доску;
|
||||
(2) `GET /api/custom-sims/:id` (ensureSpec) при загрузке спеки в iframe. Чужой draft → 403 на обоих.
|
||||
|
||||
**Что осталось / риски:**
|
||||
- Нет интеграционного теста classroom-сессии (нет харнесса; добавление потребовало бы мока сессий
|
||||
и риска зайти в зону параллельной сессии). Проверка — логическая + node --check + npm test (в baseline).
|
||||
- Время анимации не синхронится покадрово (см. выше) — by design.
|
||||
- Остаток Ф6 (UI-редактор связей в билдере + чипы в каталоге) — вне Ф7.
|
||||
|
||||
Reference in New Issue
Block a user