feat(sim-builder): фаза 5 — каталог custom-sims в /lab (LabCustom: ленивая регистрация, секция, deep-link)
This commit is contained in:
@@ -1,6 +1,33 @@
|
||||
# Feature Context: Конструктор симуляций (SimForge)
|
||||
|
||||
## Current State
|
||||
- **Фаза 5 РЕАЛИЗОВАНА** (в рабочем дереве, не закоммичено — коммит за оркестратором). Только
|
||||
**аддитивные** правки двух файлов параллельной сессии (без рефактора их кода): рабочее дерево
|
||||
по ним было ЧИСТЫМ до начала. classroom.html / backend / `_sim_deps.js` НЕ тронуты.
|
||||
- **`frontend/js/labs/lab-init.js`** (+7 строк): в начало `openSim(id)` добавлен хук
|
||||
`if (window.LabCustom && LabCustom.resolveId) id = LabCustom.resolveId(id) || id;` —
|
||||
переводит deep-link/клик `custom:<dbid>` в реестровый id `customsim_<dbid>` (LabRegistry.get/has
|
||||
обрезают часть после `:`, поэтому в реестре двоеточие недопустимо). Для встроенных id — no-op.
|
||||
- **`frontend/js/labs/lab-glue.js`**: (а) `renderSims()` merge +`&& !m._custom` (custom не в
|
||||
основной сетке) и вызов `LabCustom.renderSection(_catFilter)`; (б) init-блок (non-embed и embed)
|
||||
зовёт `LabCustom.init()`, отложенное открытие `?sim=custom:*` до загрузки списка; (в) новый
|
||||
IIFE **`window.LabCustom`** в конце файла.
|
||||
- **Поток**: `LS.customSimsList()` (мета без spec) → `_registerLazy` кладёт в LabRegistry
|
||||
манифест-заглушку `customsim_<dbid>` (`_custom:true`) с ленивым `open()`. Секция «Мои симуляции»
|
||||
`#custom-sim-section` (создаётся динамически в `#lab-home`, без правок lab.html/CSS) рендерит
|
||||
карточки из `_meta`. Открытие: `resolveId` → дисп. реестра → `open()` заглушки →
|
||||
`ensureSpec(dbid)` (`LS.customSimGet`, кэш+дедуп) → `spec.id=regId` → `registerSpecSim(spec)`
|
||||
(Ф0-адаптер, заменяет заглушку на месте) → `setActive(real)`+`real.open(ctx)` (монтирует SimEngine).
|
||||
**spec лениво** — на старте /lab не грузится. Движок (`_sim_*`) уже eager (Ф0), ленивый файл не нужен.
|
||||
- **Карточка**: preview-SVG + cat-бейдж + бейджи «Моя»(owner)/«Опубликована»(status)/«Черновик»
|
||||
+ кнопки «Редактировать»→`/sim-builder?id=<dbid>` / «Удалить»→`LS.customSimDelete` (владельцу,
|
||||
`owner_id===user.id`). Делегированный клик по `#custom-sim-section`. Иконки — inline SVG `.ic`.
|
||||
- Верификация: `node --check` обоих изменённых файлов OK; эмодзи нет (скан кодпойнтов — только
|
||||
math/box-drawing глифы ∑/═/─/→, как в существующем коде); eval/Function нет; headless-смоук
|
||||
(vm + DOM/SimEngine/LS-стабы, РЕАЛЬНЫЕ `_registry.js`+`_sim_adapter.js`) 22/22: resolveId,
|
||||
регистрация ленивых манифестов+флаг `_custom`, секция/карточки, бейджи, owner-only edit/del,
|
||||
deep-link `data-open`, lazy spec→registerSpecSim→mount, reopen синхронно, delete, встроенные не сломаны.
|
||||
git status: изменены только lab-init.js/lab-glue.js (+ плановые .md); classroom.html/backend чисты.
|
||||
- **Фаза 4 РЕАЛИЗОВАНА** (в рабочем дереве, не закоммичено — коммит за оркестратором). Только
|
||||
новые файлы `frontend/sim-builder.html` + `frontend/js/sim-builder.js` + аддитивная правка
|
||||
`js/sidebar.js` (lab.html/lab-glue.js НЕ тронуты — зона параллельной сессии).
|
||||
@@ -84,8 +111,13 @@
|
||||
- Reuse > переписывание: сначала смотреть `_fx_motion`, `_graph_panel`, `graph.js`.
|
||||
|
||||
## RESUME STATE
|
||||
- Последний коммит фичи: — (Ф0 + Ф1 + Ф2 + Ф3 + Ф4 реализованы, ещё не закоммичены — ждут оркестратора)
|
||||
- Текущая фаза: Phase 4 — Builder UI (✅ Implemented, pending commit) → дальше Phase 5 — Каталог (custom-sims в /lab)
|
||||
- Последний коммит фичи: — (Ф0 + Ф1 + Ф2 + Ф3 + Ф4 + Ф5 реализованы, ещё не закоммичены — ждут оркестратора)
|
||||
- Текущая фаза: Phase 5 — Каталог (✅ Implemented, pending commit) → дальше Phase 6 — Раздача / шаблоны / клон / программа
|
||||
- Файлы Ф5 (аддитивные правки зоны параллельной сессии — БЕЗ рефактора): `frontend/js/labs/lab-init.js`
|
||||
(+7 строк: хук `LabCustom.resolveId` в `openSim`), `frontend/js/labs/lab-glue.js` (renderSims +`!m._custom`
|
||||
и вызов renderSection; init зовёт `LabCustom.init()`; новый IIFE `window.LabCustom`). `_sim_deps.js`,
|
||||
classroom.html, backend — НЕ тронуты. Публичное API: `window.LabCustom.{init,resolveId,renderSection,ensureSpec,del}`.
|
||||
- id-неймспейс custom: deep-link/клик/`data-open` = `custom:<dbid>`; LabRegistry/host = `customsim_<dbid>`.
|
||||
- Режим: Automated / Orchestrator / Incremental
|
||||
- Файлы Ф4 (несведённые с параллельной сессией): `frontend/sim-builder.html` (new),
|
||||
`frontend/js/sim-builder.js` (new), `js/sidebar.js` (modify, аддитивный пункт `/sim-builder`).
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
- [x] Phase 2: Физический интегратор [domain: frontend] → [subplan](./phase-2-physics.md)
|
||||
- [x] Phase 3: БД + API (custom_sims) [domain: backend] → [subplan](./phase-3-persistence-api.md)
|
||||
- [x] Phase 4: Билдер (редактор) [domain: frontend] → [subplan](./phase-4-builder-ui.md)
|
||||
- [ ] Phase 5: Каталог (custom-sims в /lab) [domain: fullstack] → [subplan](./phase-5-catalog.md)
|
||||
- [x] 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)
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
| Phase 2: Physics | frontend | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 3: Persistence + API | backend | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 4: Builder UI | frontend | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 5: Catalog | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 5: Catalog | fullstack | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 6: Sharing | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 7: Classroom | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Phase 5: Каталог (custom-sims в /lab)
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Status:** ✅ Implemented (в рабочем дереве, не закоммичено — коммит за оркестратором)
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** fullstack
|
||||
|
||||
@@ -9,13 +9,21 @@
|
||||
раздел «Мои симуляции», редактирование/удаление из каталога, 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`).
|
||||
- [x] При загрузке /lab подтягивать custom-sims (свои + published) из `GET /api/custom-sims`
|
||||
(`LS.customSimsList()`) и регистрировать ЛЕНИВЫЕ манифесты в LabRegistry. spec НЕ грузится
|
||||
на старте — тянется при первом открытии (`LS.customSimGet` → `registerSpecSim` Ф0-адаптера).
|
||||
- [x] Карточки в каталоге: категория/предмет(grade)/из меты; бейдж «Моя» (owner) / «Опубликована»
|
||||
(status) / «Черновик» (own draft).
|
||||
- [x] Раздел «Мои симуляции» в /lab — отдельная секция `#custom-sim-section` в `#lab-home`
|
||||
(создаётся динамически, без правок lab.html/CSS); уважает текущий фильтр категорий.
|
||||
- [x] Кнопки на карточке custom-sim: «Редактировать» → `/sim-builder?id=<dbid>`, «Удалить»
|
||||
(`LS.customSimDelete`) — только владельцу (`owner_id === user.id`).
|
||||
- [x] Deep-link `/lab?sim=custom:<dbid>` открывает напрямую: хук `LabCustom.resolveId` в `openSim`
|
||||
(lab-init.js) переводит `custom:<dbid>` → реестровый id `customsim_<dbid>`; для custom deep-link
|
||||
открытие отложено до загрузки списка (и в обычном, и в embed-режиме).
|
||||
- [x] Ленивая загрузка: движок (`_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)
|
||||
@@ -33,10 +41,57 @@
|
||||
- id-неймспейс `custom:` чтобы не конфликтовать со встроенными.
|
||||
|
||||
## Review Checklist
|
||||
- [ ] Все задачи выполнены
|
||||
- [ ] Встроенные симуляции и старт /lab не регрессировали
|
||||
- [ ] Draft видит только владелец; published — все
|
||||
- [ ] Ленивая загрузка движка работает
|
||||
- [x] Все задачи выполнены
|
||||
- [x] Встроенные симуляции и старт /lab не регрессировали (custom исключены из основной сетки
|
||||
по флагу `_custom`; падение загрузки списка не ломает каталог — try/catch + мягкий warn)
|
||||
- [x] Draft видит только владелец; published — все (видимость обеспечивает сервер Ф3:
|
||||
`customSimsList` отдаёт свои любого статуса + чужие published; бейджи/кнопки — по `owner_id`)
|
||||
- [x] Ленивая загрузка 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>')` → `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()`.
|
||||
|
||||
Reference in New Issue
Block a user