Files
Learn_System/plans/sim-builder/phase-5-catalog.md
T

8.5 KiB
Raw Blame History

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.customSimGetregisterSpecSim Ф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> → реестровый id customsim_<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> → реестровый id customsim_<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 попадают в каталог и открываются

  1. LS.customSimsList() → мета (без spec). Для каждой — _registerLazy(meta): в LabRegistry кладётся манифест-заглушка id='customsim_<dbid>', _custom:true, мета-поля, и open(), который при первом вызове лениво тянет spec.
  2. Секция «Мои симуляции» (#custom-sim-section) рендерится из _meta (НЕ из реестра): карточки .sim-card с data-open="custom:<dbid>", бейджами и (владельцу) кнопками edit/del. Делегат кликов на секции: открыть / /sim-builder?id= / LS.customSimDelete.
  3. Открытие: openSim('custom:<dbid>')resolveIdcustomsim_<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().