8.5 KiB
8.5 KiB
Phase 5: Каталог (custom-sims в /lab)
Status: ✅ Implemented (в рабочем дереве, не закоммичено — коммит за оркестратором) Parent plan: PLAN.md Domain: fullstack
Objective
Сохранённые custom-симуляции появляются и играют в /lab наравне со встроенными; раздел «Мои симуляции», редактирование/удаление из каталога, deep-link.
Tasks
- При загрузке /lab подтягивать custom-sims (свои + published) из
GET /api/custom-sims(LS.customSimsList()) и регистрировать ЛЕНИВЫЕ манифесты в LabRegistry. spec НЕ грузится на старте — тянется при первом открытии (LS.customSimGet→registerSpecSimФ0-адаптера). - Карточки в каталоге: категория/предмет(grade)/из меты; бейдж «Моя» (owner) / «Опубликована» (status) / «Черновик» (own draft).
- Раздел «Мои симуляции» в /lab — отдельная секция
#custom-sim-sectionв#lab-home(создаётся динамически, без правок lab.html/CSS); уважает текущий фильтр категорий. - Кнопки на карточке custom-sim: «Редактировать» →
/sim-builder?id=<dbid>, «Удалить» (LS.customSimDelete) — только владельцу (owner_id === user.id). - Deep-link
/lab?sim=custom:<dbid>открывает напрямую: хукLabCustom.resolveIdвopenSim(lab-init.js) переводитcustom:<dbid>→ реестровый idcustomsim_<dbid>; для custom deep-link открытие отложено до загрузки списка (и в обычном, и в embed-режиме). - Ленивая загрузка: движок (
_sim_expr/_sim_engine/_sim_adapter) уже eager в lab.html (Ф0), поэтому отдельный ленивый файл не нужен; лениво грузится только spec (тяжёлый JSON) при открытии._sim_deps.jsНЕ тронут.
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 не регрессировали (custom исключены из основной сетки
по флагу
_custom; падение загрузки списка не ломает каталог — try/catch + мягкий warn) - Draft видит только владелец; published — все (видимость обеспечивает сервер Ф3:
customSimsListотдаёт свои любого статуса + чужие published; бейджи/кнопки — поowner_id) - Ленивая загрузка spec работает (кэш + дедуп; на старте спеки не грузятся)
Handoff to Next Phase
Что реализовано (Phase 5)
Только аддитивные правки двух файлов параллельной сессии — без рефактора их кода:
frontend/js/labs/lab-init.js(+7 строк): в началоopenSim(id)добавлен хук —if (window.LabCustom && LabCustom.resolveId) id = LabCustom.resolveId(id) || id;Переводитcustom:<dbid>→ реестровый idcustomsim_<dbid>(LabRegistry.get/has обрезают часть после:, поэтому в реестре двоеточие недопустимо). Для встроенных id — no-op.frontend/js/labs/lab-glue.js:renderSims(): 1 строка в merge —&& !m._custom(custom-манифесты не попадают в основную сетку встроенных) + вызовLabCustom.renderSection(_catFilter)после рендера грида.- init-блок (non-embed): после
renderSims()зовётLabCustom.init(); для?sim=custom:*открытие отложено до резолва списка. Аналогично в embed-ветке (наload). window.LabCustom(новый IIFE в конце файла):init()(fetch списка, регистрация ленивых манифестов, рендер секции),resolveId,renderSection,ensureSpec,del.
Как custom-sims попадают в каталог и открываются
LS.customSimsList()→ мета (без spec). Для каждой —_registerLazy(meta): в LabRegistry кладётся манифест-заглушкаid='customsim_<dbid>',_custom:true, мета-поля, иopen(), который при первом вызове лениво тянет spec.- Секция «Мои симуляции» (
#custom-sim-section) рендерится из_meta(НЕ из реестра): карточки.sim-cardсdata-open="custom:<dbid>", бейджами и (владельцу) кнопками edit/del. Делегат кликов на секции: открыть //sim-builder?id=/LS.customSimDelete. - Открытие:
openSim('custom:<dbid>')→resolveId→customsim_<dbid>→ дисп. реестра →open()заглушки →ensureSpec(dbid)(LS.customSimGet, кэш+дедуп) →spec.id=<regId>→registerSpecSim(spec)(Ф0-адаптер строит реальный SimEngine-манифест, ЗАМЕНЯЕТ заглушку на месте) →setActive(real)+real.open(ctx)(монтирует SimEngine). Повторное открытие — синхронно, реальный манифест уже в реестре, spec из кэша.
id-неймспейс
- Deep-link / клик /
data-open:custom:<dbid>. - LabRegistry / host:
customsim_<dbid>(без:). Конвертация — только вresolveId.
Формат карточки
preview-SVG (плейсхолдер) + cat-бейдж (.sim-cat) + бейджи «Моя»/«Опубликована»/«Черновик»
- title + desc (+ «N класс») + (владельцу) ряд кнопок «Редактировать»/«Удалить» (inline SVG
.ic).
Риски / заметки для Ф6
_loadRelated('customsim_<dbid>')дергает/api/lab/sims/.../related(404 для custom) — тихо глотается. Если Ф6 заведёт связи custom-sim с программой — учесть этот id.- Удаление: после
customSimDeleteкарточка убирается из секции, но манифест-заглушка остаётся в LabRegistry (LabRegistry не имеет unregister). Не критично (карточки нет, deep-link на удалённую вернёт 404 при ensureSpec). Если Ф6/Ф7 потребуют чистку — добавить unregister в реестр. - Ф6 (раздача/публикация/клон/шаблоны): кнопку «Поделиться/Раздать классу» добавлять в
_cardHtml(ещё одинdata-act); публикацию toggle — там же. Клон — новыйLS.customSimCreateсо spec изensureSpec. Источник spec для доски (Ф7) —LabCustom.ensureSpec(dbid)или живойLabRegistry.get('customsim_<dbid>').instance().