chore(plan): lab-split 5-phase plan
PLAN.md + 5 subplans + CONTEXT.md Strategy: Incremental | Mode: Automated | Execution: Direct Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
# Feature Context: Lab.html Split
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
- lab.html: 5180L total
|
||||||
|
- Lines 10-866: inline `<style>` block (856L)
|
||||||
|
- Lines 868-4303: HTML body with 39 sim-panels
|
||||||
|
- Lines 4304-4326: external script tags (engine modules)
|
||||||
|
- Lines 4327-5152: inline `<script>` block (825L glue)
|
||||||
|
- Lines 5153+: more script tags
|
||||||
|
- 39 engine modules already extracted in `frontend/js/labs/*.js`
|
||||||
|
- `lab-init.js` (543L) is the orchestrator
|
||||||
|
- 265 hardcoded brand colors throughout
|
||||||
|
- 1017 inline `style=` attributes
|
||||||
|
|
||||||
|
## Key Discoveries
|
||||||
|
|
||||||
|
- Each sim has a `<div id="sim-X" class="sim-proj-wrap" style="display:none">` pattern
|
||||||
|
- Sim activation likely via `sim-switcher` element + JS that toggles display
|
||||||
|
- Lab has its own large CSS scope that doesn't conflict with ls.css (verify)
|
||||||
|
|
||||||
|
## Cross-Phase Dependencies
|
||||||
|
|
||||||
|
- **Phase 1** (extract style) — independent
|
||||||
|
- **Phase 2** (extract glue) — independent of Phase 1
|
||||||
|
- **Phase 3** (token purification) — can run after Phase 1 (CSS file becomes purification target)
|
||||||
|
- **Phase 4** (hash-router) — needs Phase 2 (router code in lab-glue.js easier to extend than inline)
|
||||||
|
- **Phase 5** (template lazy) — needs Phase 4 (router triggers template activation)
|
||||||
|
|
||||||
|
## Temporary Workarounds
|
||||||
|
|
||||||
|
(пусто — заполняется implementer'ом)
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
### Что НЕ трогаем
|
||||||
|
|
||||||
|
- `frontend/js/labs/*.js` engine-классы (CollisionSim, ProjectileSim и т.д.) — они работают
|
||||||
|
- `frontend/js/labs/lab-init.js` — orchestrator, может расширяться, но не переписываться целиком
|
||||||
|
- `<canvas>` элементы и их id — engine-классы binds к ним по id
|
||||||
|
|
||||||
|
### Безопасные паттерны (из прошлой работы)
|
||||||
|
|
||||||
|
- Extract `<style>` → external file: добавить `<link rel="stylesheet">` в head, скопировать содержимое в .css, удалить inline. Проверить визуал curl-200 + spot-check styles.
|
||||||
|
- Extract `<script>` → external file: добавить `<script src=>` ПОСЛЕ engine-modules, переместить glue. Watch for global-leak (если inline relies on top-level vars).
|
||||||
|
- Token purification: replace `#9B5DE5` → `var(--violet)`, keep tints (`rgba(...)`), keep curated palettes.
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# Feature: Lab.html Split
|
||||||
|
|
||||||
|
**Branch:** `feature/lab-split`
|
||||||
|
**Base branch:** `master`
|
||||||
|
**Created:** 2026-05-22
|
||||||
|
**Status:** 🟡 In Progress
|
||||||
|
**Strategy:** Incremental
|
||||||
|
**Mode:** Automated
|
||||||
|
**Execution:** Direct
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Расщепить `frontend/lab.html` (5180L монолит UI-shell) на модульную структуру: extract inline CSS + inline JS-glue, token purification (265 hardcodes → vars), hash-router для deep-links, optional `<template>` lazy-mount.
|
||||||
|
|
||||||
|
**Discovery:** симуляции уже extracted в `frontend/js/labs/*.js` (39 engine-классов). lab.html — это HTML shell с 856L inline CSS + 3435L DOM + 825L inline glue-JS.
|
||||||
|
|
||||||
|
**Цели:**
|
||||||
|
- lab.html structure clearer (CSS / JS вынесены, HTML только DOM)
|
||||||
|
- Хардкодов ≤ 30 (от 265)
|
||||||
|
- Deep-link `#sim/projectile` работает
|
||||||
|
- Все 39 симуляций без регрессий
|
||||||
|
- Pre-commit hook проходит на каждой фазе
|
||||||
|
|
||||||
|
## Build & Test Commands
|
||||||
|
|
||||||
|
- **Start:** `cd backend && npm start` (vanilla JS — нет билда)
|
||||||
|
- **Test:** `cd backend && npm test` (66 tests / 63 pass / 3 baseline-fail)
|
||||||
|
- **Lint:** `cd backend && npm run lint:routes`
|
||||||
|
- **Smoke:** `curl -sI http://localhost:3000/lab` → 200; манульно открыть несколько sim
|
||||||
|
- **Pre-commit hook активен** — runs all of the above automatically
|
||||||
|
|
||||||
|
## Phases
|
||||||
|
|
||||||
|
- [ ] Phase 1: Extract inline `<style>` → `frontend/css/lab.css` [domain: frontend] → [subplan](./phase-1-extract-style.md)
|
||||||
|
- [ ] Phase 2: Extract inline glue `<script>` → `frontend/js/labs/lab-glue.js` [domain: frontend] → [subplan](./phase-2-extract-glue.md)
|
||||||
|
- [ ] Phase 3: Token purification (265 hardcodes) [domain: frontend] → [subplan](./phase-3-token-purify.md) (parallelizable with 1 или 2)
|
||||||
|
- [ ] Phase 4: Hash-router for sim deep-links [domain: frontend] → [subplan](./phase-4-hash-router.md)
|
||||||
|
- [ ] Phase 5: `<template>` lazy-mount (stretch) [domain: frontend] → [subplan](./phase-5-template-lazy.md)
|
||||||
|
|
||||||
|
## Phase Progress Log
|
||||||
|
|
||||||
|
| Phase | Domain | Status | Review | Build | Committed |
|
||||||
|
|-------|--------|--------|--------|-------|-----------|
|
||||||
|
| Phase 1: Extract style | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||||
|
| Phase 2: Extract glue | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||||
|
| Phase 3: Token purify | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||||
|
| Phase 4: Hash-router | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||||
|
| Phase 5: Template lazy | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||||
|
|
||||||
|
## Final Review
|
||||||
|
- [ ] Comprehensive code review
|
||||||
|
- [ ] All 39 simulations smoke-tested
|
||||||
|
- [ ] No console errors on /lab
|
||||||
|
- [ ] pre-commit hook passes
|
||||||
|
- [ ] Merged to `master`
|
||||||
|
|
||||||
|
## Acceptance Criteria (whole feature)
|
||||||
|
|
||||||
|
- lab.html без inline `<style>` блока (856L moved out)
|
||||||
|
- lab.html без inline glue `<script>` блока (825L moved out)
|
||||||
|
- Хардкодов brand colors ≤ 30 (curated palettes сохраняются)
|
||||||
|
- `#sim/projectile`, `#sim/newton` и др. — deep-link работают, F5 восстанавливает
|
||||||
|
- 39 симуляций функциональны (canvas рендерится, кнопки работают)
|
||||||
|
- Pre-commit hook чистый на каждом коммите
|
||||||
|
|
||||||
|
## Tech Stack & Conventions Reference
|
||||||
|
|
||||||
|
- vanilla JS, no bundler, Express static serve
|
||||||
|
- `window.LS.*` namespace (api.js)
|
||||||
|
- `LS.modal`, `LS.confirm`, `LS.toast`
|
||||||
|
- ls.css design tokens (--violet/--cyan/--green/--pink/--amber + --text/--text-2/--text-3 + spacing + radii)
|
||||||
|
- Lucide icons (CDN) + inline SVG `.ic`
|
||||||
|
- **No emoji в коде** (pre-commit блокирует)
|
||||||
|
- **No grep tool**, только ast-index для search
|
||||||
|
- Existing labs/*.js engine-классы НЕ трогаем — они уже extracted
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Phase 1: Extract inline `<style>` → `frontend/css/lab.css`
|
||||||
|
|
||||||
|
**Status:** ⬜ Not Started
|
||||||
|
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||||
|
**Domain:** frontend
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Вынести 856L inline `<style>` блока из lab.html в отдельный `frontend/css/lab.css`. После — lab.html становится короче на ~856L, CSS можно править/рефакторить независимо.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
- [ ] Создать `frontend/css/lab.css`
|
||||||
|
- [ ] Скопировать содержимое `<style>...</style>` из lab.html (lines 10-866) в lab.css
|
||||||
|
- [ ] Удалить inline `<style>` блок из lab.html
|
||||||
|
- [ ] Добавить `<link rel="stylesheet" href="/css/lab.css">` в `<head>` (после `/css/ls.css`)
|
||||||
|
- [ ] Verify: `curl -sI http://localhost:3000/lab` → 200
|
||||||
|
- [ ] Spot-check: открыть в браузере, sim-toolbar/panels выглядят как раньше
|
||||||
|
- [ ] Pre-commit hook passes
|
||||||
|
|
||||||
|
## Files to Modify/Create
|
||||||
|
|
||||||
|
- `frontend/css/lab.css` — NEW (~856L)
|
||||||
|
- `frontend/lab.html` — удалить `<style>` блок, добавить `<link>` (net −855L)
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- lab.html без `<style>` блока (только `<link>` к /css/lab.css)
|
||||||
|
- `/lab` отвечает 200
|
||||||
|
- Визуально lab выглядит идентично pre-Phase-1
|
||||||
|
- Pre-commit hook чистый
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Если в CSS есть `@import` / `url(...)` paths — проверить что они всё ещё валидны от нового origin (/css/lab.css base)
|
||||||
|
- CSP в server.js: разрешает `'self'` для styles, нет проблем
|
||||||
|
- Watch for: CSS-variables defined inline могут оказаться нужны другим inline blocks → проверить нет ли таких dependencies
|
||||||
|
|
||||||
|
## Review Checklist
|
||||||
|
|
||||||
|
- [ ] lab.css не пустой и содержит весь CSS из inline блока
|
||||||
|
- [ ] lab.html не содержит `<style>` блок (только `<link>`)
|
||||||
|
- [ ] No emoji в коде (pre-commit проверит)
|
||||||
|
- [ ] Server возвращает 200
|
||||||
|
- [ ] Spot-check: открыть /lab, sim-grid и sim-toolbar отображаются нормально
|
||||||
|
|
||||||
|
## Handoff to Next Phase
|
||||||
|
|
||||||
|
<!-- Implementer заполнит: какой именно баг был при extract (если был), какие inline-style overrides остались (Phase 3 будет с ними работать). -->
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Phase 2: Extract inline `<script>` glue → `frontend/js/labs/lab-glue.js`
|
||||||
|
|
||||||
|
**Status:** ⬜ Not Started
|
||||||
|
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||||
|
**Domain:** frontend
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Вынести 825L inline `<script>` блока (lines 4327-5152) из lab.html в отдельный `frontend/js/labs/lab-glue.js`. Этот блок содержит glue-код (sim-switcher logic, init helpers, event wiring). lab-init.js остаётся orchestrator'ом.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
- [ ] Определить точные границы inline `<script>` блока (line 4327 start, find matching `</script>`)
|
||||||
|
- [ ] Создать `frontend/js/labs/lab-glue.js`
|
||||||
|
- [ ] Скопировать содержимое в lab-glue.js
|
||||||
|
- [ ] **Сохранить порядок загрузки**: lab-glue.js должен подгружаться ПОСЛЕ всех `labs/*.js` engine-модулей И ПЕРЕД `labs/lab-init.js` (или после — зависит от dependencies, проверить!)
|
||||||
|
- [ ] Удалить inline блок из lab.html
|
||||||
|
- [ ] Добавить `<script src="/js/labs/lab-glue.js"></script>` в правильное место
|
||||||
|
- [ ] Verify: page loads, `console.log` ошибок нет, sim-switcher работает
|
||||||
|
- [ ] Smoke: переключить 3-4 разных sim, проверить что render запускается
|
||||||
|
|
||||||
|
## Files to Modify/Create
|
||||||
|
|
||||||
|
- `frontend/js/labs/lab-glue.js` — NEW (~825L)
|
||||||
|
- `frontend/lab.html` — удалить inline `<script>` блок, добавить `<script src>` тег (net −823L)
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- lab.html без большого inline `<script>` блока на lines 4327-5152
|
||||||
|
- `/lab` отвечает 200
|
||||||
|
- No `ReferenceError` / `is not defined` в console (load-order правильный)
|
||||||
|
- Sim-switcher переключает sims корректно
|
||||||
|
- 5 любых симуляций инициализируются и рендерятся
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
### Load-order анализ
|
||||||
|
|
||||||
|
Перед extract — проверить какие globals использует inline glue:
|
||||||
|
- Если использует `CollisionSim` (из engine-modules) → нужно загружаться ПОСЛЕ engine-modules
|
||||||
|
- Если использует `Lucide` (CDN) → после Lucide
|
||||||
|
- Если других inline-vars нет — безопасно вынести
|
||||||
|
|
||||||
|
### Watch for
|
||||||
|
|
||||||
|
- Inline `<script>` без `defer` атрибута выполняется sync — после переноса в external может выполниться раньше DOM ready. Возможно нужен `DOMContentLoaded` wrapper, либо `defer` атрибут.
|
||||||
|
- `window.xxx = ...` глобальные exports должны остаться (onclick handlers HTML on них опираются)
|
||||||
|
|
||||||
|
### Strategy
|
||||||
|
|
||||||
|
1. Read весь inline блок
|
||||||
|
2. Identify все function/var declarations
|
||||||
|
3. Скопировать как есть в lab-glue.js
|
||||||
|
4. Add at top: `'use strict';` если ещё нет
|
||||||
|
5. Тестировать пристально
|
||||||
|
|
||||||
|
## Review Checklist
|
||||||
|
|
||||||
|
- [ ] lab-glue.js загружается в правильном порядке (после engine modules)
|
||||||
|
- [ ] No console errors на /lab
|
||||||
|
- [ ] Sim-switcher работает (тест: переключить projectile → newton → chemsandbox)
|
||||||
|
- [ ] Все onclick handlers HTML работают
|
||||||
|
- [ ] No emoji в коде
|
||||||
|
- [ ] Pre-commit hook passes
|
||||||
|
|
||||||
|
## Handoff to Next Phase
|
||||||
|
|
||||||
|
<!-- Implementer фиксирует: где находится hash-router-точка-расширения (Phase 4 будет добавлять hashchange handler сюда). -->
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Phase 3: Token purification — 265 hardcoded colors → vars
|
||||||
|
|
||||||
|
**Status:** ⬜ Not Started
|
||||||
|
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||||
|
**Domain:** frontend
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Заменить 265 хардкодных brand-цветов в lab-related files на `var(--token)` где это семантически корректно. Сохранить curated palettes (subject-specific colors) и canvas-fillStyle (CSS vars не resolve'ятся в canvas context).
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
- [ ] Идентифицировать все хардкоды в:
|
||||||
|
- `frontend/lab.html` (HTML body + remaining inline styles)
|
||||||
|
- `frontend/css/lab.css` (после Phase 1)
|
||||||
|
- `frontend/js/labs/lab-glue.js` (после Phase 2)
|
||||||
|
- [ ] Заменить direct token matches:
|
||||||
|
- `#9B5DE5` → `var(--violet)`
|
||||||
|
- `#06D6E0` → `var(--cyan)`
|
||||||
|
- `#06D664` → `var(--green)` (also `#06D6A0` если есть)
|
||||||
|
- `#F15BB5` → `var(--pink)`
|
||||||
|
- `#FFB347` → `var(--amber)`
|
||||||
|
- `#0F172A` → `var(--text)`
|
||||||
|
- `#3D4F6B` → `var(--text-2)`
|
||||||
|
- `#56687A` → `var(--text-3)`
|
||||||
|
- `#EEF2FF` → `var(--bg)`
|
||||||
|
- [ ] KEEP (НЕ менять):
|
||||||
|
- Tinted/alpha (`rgba(155,93,229,0.12)` etc.) — CSS не имеет color-mix() без deps
|
||||||
|
- Canvas `ctx.fillStyle = "#..."` — CSS vars не работают в canvas
|
||||||
|
- Curated subject palettes (bio violet / chem green / math cyan / phys amber) если они вложены массивом
|
||||||
|
- Slightly-different shades (`#9B5DE6` ≠ `#9B5DE5`) — это намеренно другой цвет
|
||||||
|
- [ ] Semantic aliases: использовать `var(--danger)` / `var(--success)` / `var(--warning)` / `var(--info)` в semantic context
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
- `frontend/lab.html` — заменить hardcodes
|
||||||
|
- `frontend/css/lab.css` (после Phase 1) — заменить hardcodes
|
||||||
|
- `frontend/js/labs/lab-glue.js` (после Phase 2) — заменить hardcodes если есть
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- Хардкодов brand-colors ≤ 30 (от 265, target 90%+ replacement)
|
||||||
|
- Визуально lab выглядит идентично
|
||||||
|
- Pre-commit hook passes
|
||||||
|
- No regression в любой sim
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Lab.html сейчас содержит много inline `style="color: #XXX"` — multi-line replacement через ast-index search + manual replacement
|
||||||
|
- Don't over-aggressive — если color используется один раз в curated palette decoration, лучше keep
|
||||||
|
- Document log: "Заменено: N, оставлено: M (reasons listed)"
|
||||||
|
|
||||||
|
## Review Checklist
|
||||||
|
|
||||||
|
- [ ] Counts reported: before / after
|
||||||
|
- [ ] Spot-check 3 sims визуально (canvas рендеринг не поменялся)
|
||||||
|
- [ ] No emoji в коде
|
||||||
|
- [ ] Pre-commit hook passes
|
||||||
|
|
||||||
|
## Handoff to Next Phase
|
||||||
|
|
||||||
|
<!-- Implementer: какие места были tricky, есть ли паттерны которые нужно унифицировать в будущем. -->
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# Phase 4: Hash-router для sim deep-links
|
||||||
|
|
||||||
|
**Status:** ⬜ Not Started
|
||||||
|
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||||
|
**Domain:** frontend
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Сделать `#sim/projectile`, `#sim/newton`, `#sim/chemsandbox` etc. → открывают конкретный sim в lab. F5 на любом deep-link восстанавливает sim. Browser back/forward переключают между симуляциями. По образцу admin-redesign Phase 1 router.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
- [ ] В `frontend/js/labs/lab-glue.js` (или новый `frontend/js/labs/lab-router.js`):
|
||||||
|
- На load: прочитать `location.hash`, parsr `#sim/<name>`, активировать соответствующий sim
|
||||||
|
- Listen `hashchange`: при изменении hash → переключить sim
|
||||||
|
- При программном переключении sim (через sim-switcher UI) → обновить hash без recursion (флаг `_routerNavigating`)
|
||||||
|
- [ ] Map sim-name → sim-id:
|
||||||
|
- `#sim/projectile` → activate `<div id="sim-proj">`
|
||||||
|
- `#sim/newton` → activate `<div id="sim-dynamics">` (или какой-там)
|
||||||
|
- Полный mapping из существующих sim-ID
|
||||||
|
- [ ] Fallback: unknown hash → ignore (показать default sim)
|
||||||
|
- [ ] Verify: F5 на `/lab#sim/projectile` восстанавливает projectile sim
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
- `frontend/js/labs/lab-glue.js` — добавить router code (~50-100L)
|
||||||
|
- `frontend/lab.html` — без изменений (или + 1 script tag если делаем отдельный lab-router.js)
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- F5 на `/lab#sim/X` восстанавливает соответствующий sim
|
||||||
|
- Browser back/forward переключают между sims
|
||||||
|
- Click на sim-switcher обновляет URL (`#sim/X` в адресной строке)
|
||||||
|
- Unknown hash (`#sim/nonexistent`) → console.warn + fallback на default
|
||||||
|
- 5 deep-link проверены вручную (projectile, newton, chemsandbox, gas, mirror)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
### Recursion guard pattern (из admin-redesign Phase 1)
|
||||||
|
|
||||||
|
```js
|
||||||
|
let _navigating = false;
|
||||||
|
function navigateTo(simId) {
|
||||||
|
_navigating = true;
|
||||||
|
location.hash = '#sim/' + simId;
|
||||||
|
setTimeout(() => { _navigating = false; }, 0);
|
||||||
|
activateSim(simId);
|
||||||
|
}
|
||||||
|
window.addEventListener('hashchange', () => {
|
||||||
|
if (_navigating) return;
|
||||||
|
const m = location.hash.match(/^#sim\/([\w-]+)/);
|
||||||
|
if (m) activateSim(m[1]);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reference
|
||||||
|
|
||||||
|
`frontend/js/admin/router.js` (admin-redesign Phase 1) — read for inspiration. Adapt to lab context.
|
||||||
|
|
||||||
|
## Review Checklist
|
||||||
|
|
||||||
|
- [ ] Hash deep-link работает для 5 проверенных симов
|
||||||
|
- [ ] Browser back/forward работают
|
||||||
|
- [ ] No console errors
|
||||||
|
- [ ] No infinite-loop при программной активации
|
||||||
|
- [ ] Pre-commit hook passes
|
||||||
|
|
||||||
|
## Handoff to Next Phase
|
||||||
|
|
||||||
|
<!-- Implementer: full mapping #sim/X → sim-ID для Phase 5 (template lazy). -->
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# Phase 5: `<template>` lazy-mount (stretch goal)
|
||||||
|
|
||||||
|
**Status:** ⬜ Not Started
|
||||||
|
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||||
|
**Domain:** frontend
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Уменьшить initial DOM size: вместо 39 `<div id="sim-X">` с `display:none` — обернуть каждую в `<template id="tpl-sim-X">`, активация sim → clone template + mount into placeholder. Initial page load становится легче (нет render'а скрытых canvas-ов).
|
||||||
|
|
||||||
|
**ВНИМАНИЕ:** Это stretch goal — может быть пропущен если Phase 1-4 успешны и не хочется добавлять риск.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
- [ ] Identify все `<div id="sim-X" class="sim-proj-wrap" style="display:none">` блоки (~38 штук, скрытых; 1 default visible)
|
||||||
|
- [ ] Обернуть каждый в `<template id="tpl-sim-X">...</template>`
|
||||||
|
- [ ] Создать mount point `<div id="sim-mount"></div>` (или использовать existing #sim-grid)
|
||||||
|
- [ ] В lab-glue.js / lab-router.js:
|
||||||
|
- `activateSim(name)` → clone `<template id="tpl-sim-X">` → replace content of `#sim-mount`
|
||||||
|
- Initialize sim engine (CollisionSim, ProjectileSim, etc.) **после** mount'а
|
||||||
|
- Cleanup previous sim engine (stop animations, remove listeners) перед switching
|
||||||
|
- [ ] Test: переключить 5 sims подряд, no memory leak, animations stop когда sim de-activated
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
- `frontend/lab.html` — wrap 38+ `<div id="sim-X">` в `<template>` (большое change but mechanical)
|
||||||
|
- `frontend/js/labs/lab-glue.js` — add template-clone activation logic
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- Initial DOM size уменьшен (verify через DevTools — count nodes)
|
||||||
|
- Все 39 sims активируются и работают
|
||||||
|
- No memory leak при переключении (verify через DevTools Performance / Memory tab — heap не растёт неограниченно)
|
||||||
|
- Sim engines properly cleanup previous instance
|
||||||
|
|
||||||
|
## Risks (HIGH)
|
||||||
|
|
||||||
|
- **Sim engines binds to specific canvas IDs by JS** (e.g. `document.getElementById('canvas-proj')`). После template clone — `getElementById` может вернуть element до момента когда engine ищет его. Time it carefully.
|
||||||
|
- **Animations не stop'аются** автоматически при template removal. Engine классы должны иметь `destroy()` метод — проверить.
|
||||||
|
- **Initial sim** (тот что default visible) — изначально mount'ed, не template. Обработать особо.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Если в течение фазы выясняется что engine классы плохо cleanup'ятся (нет `destroy()`, requestAnimationFrame продолжает работать), MARK phase as 🟡 partial и оставить как known limitation. Не нужно рефакторить 30 engine классов.
|
||||||
|
|
||||||
|
## Mitigation: opt-out
|
||||||
|
|
||||||
|
Если эта фаза создаёт реальный риск — **пропустить**. lab.html без Phase 5 будет ~4000L (после Phase 1 убрали 856L style + 825L glue), что уже сильно лучше 5180L. Phase 5 опционален.
|
||||||
|
|
||||||
|
## Review Checklist
|
||||||
|
|
||||||
|
- [ ] All 39 sims активируются успешно
|
||||||
|
- [ ] No memory leak при переключении (Heap snapshot до и после — diff не растёт)
|
||||||
|
- [ ] Browser DevTools showsменьше nodes на initial load
|
||||||
|
- [ ] No console errors
|
||||||
|
|
||||||
|
## Handoff to Next Phase
|
||||||
|
|
||||||
|
<!-- Финальная фаза. Implementer записывает: что осталось как known-limitation, какие engine классы не имеют destroy(). -->
|
||||||
Reference in New Issue
Block a user