Files
Learn_System/plans/sim-builder/phase-7-classroom.md
T

7.3 KiB

Phase 7: Доска онлайн-урока

Status: Implemented (pending commit) Parent plan: PLAN.md Domain: fullstack

Objective

Открывать custom-симуляцию на доске онлайн-урока через существующий конвейер встраивания sim, с синхроном параметров классу и аннотациями поверх.

Tasks

  • Учитель в classroom выбирает sim для доски: добавить в список источников свои+published custom-sims (рядом со встроенными). → _crLoadCustomSims() (LS.customSimsList) + мёрж в _crRenderSimGrid; карточка с бейджем «Моя», id=custom:<dbid>.
  • Открытие на доске через существующий 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.
  • Синхрон состояния: параметры + play/pause транслируются классу через существующий мост sim_state/apply_sim_state (lab-glue) → simState. Custom-sim подключён к мосту через _bridgeCustomSimState(real) (getState/applyState поверх SimEngine.params/setParam/play/pause). ⚠️ Время t (фаза анимации) жёстко НЕ синхронится — только параметры слайдеров + признак running.
  • Аннотации поверх — через существующий simAnnotate (без изменений конвейера, id-agnostic).
  • Закрытие/смена sim: onSimClose сбрасывает frame.src='about:blank' → весь документ iframe (SimEngine + rAF + слушатели + реестр состояния) сносится вместе с iframe. Утечек нет.
  • Проверка: логическая (трасса teacher→backend→SSE→student) + node --check + npm test (в baseline). Headless classroom тяжёл; синхрон проверен по конвейеру, открытие/доступ/закрытие — по коду.

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

  • Все задачи выполнены
  • Синхрон параметров учитель→ученики работает (sim_state мост, params+running)
  • Доступ к custom-sim на доске проверяется (server simOpen: own|published|admin; иначе 403/404)
  • Встроенные 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.