Миниатюры: uploadPersonalFile генерирует webp ≤480px (sharp), возвращает {url, thumbUrl}; колонка thumb_url (мигр.074); грид рисует <img> на миниатюре, просмотр/скачивание/аннотация — полный url. Ссылочная чистка матчит url И thumb_url; share копирует thumb; квота учитывает файл+миниатюру. Сейверы board-clip/material-save/textbook-clip/draw пробрасывают thumbUrl.
Пагинация: клиент рендерит PAGE_SIZE=60 карточек + «Показать ещё» (сброс на смену фильтра), сохраняя клиентский поиск/сортировку над полным списком.
Тесты: materials.test.js 16→19. План V2 выполнен.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.5 KiB
«Мои материалы» — v2: харднинг и доводка
Составлен Opus 2026-06-13. Базовый план (PLAN.md, Фазы 1–6) полностью реализован. Его раздел «Сквозные риски» отложил ровно то, что закрывает этот план: учёт/лимиты/чистку хранилища и
materials.test.js. Источник истины по текущему состоянию — код (studentMaterialsController.js,materials.js,my-materials.html,board-clip.js,material-save.js) и reference_student_materials.
Готчи проекта: новый :id-роут → // @public-by-design + проверка владельца; большие HTML — только Edit;
без эмодзи (inline SVG .ic); коммит поимённо + push; перезапуск сервера при правке backend; ветка
feature/sim-builder в рабочем дереве — НЕ коммитить чужие правки, только свои файлы.
Фаза 1 — Целостность и безопасность (backend, фундамент) ✅ цель этого захода
- Ссылочно-подсчётная чистка файлов.
DELETE /:idи сменаurl(аннотация) сейчас оставляют файл вuploads/materials/сиротой.shareкопируетurlдословно → несколько строк ссылаются на ОДИН файл, поэтомуunlinkтолько когда наurlне ссылается ни одна строка. ХелперreleaseFileForUrl(url)вызывается ПОСЛЕ delete/update. - Allowlist схемы URL.
create/updateпринимали любойurl→linkсо схемойjavascript:рендерится как рабочий<a href>(раздача делает это вектором учитель→ученики). ХелперsafeUrl: толькоhttp(s)://или app-relative/…(не//host); иначе 400. - Квота на пользователя. Колонка
bytes(мигр. 073), счётSUM(bytes)/COUNT(*). Лимит по числу материалов — вcreate(); лимит по байтам — вuploadPersonalFile(до приёма файла). Конфигурируемо черезMATERIALS_MAX_ITEMS/MATERIALS_MAX_BYTES(для тестов — низкий потолок). backend/tests/materials.test.js— CRUD, владелец (403/404), коллекции, share-копия + роль/owner, валидация URL, лимит числа, ссылочная чистка (прямой вызов хелпера на временном файле).
Фаза 2 — Производительность ✅
- ✅
GET /api/materialsотдаёт обрезанныйbody(первые 1000 симв.) + флагbody_trunc; полный текст — ленивыйGET /api/materials/:id(getOne, owner-only). КлиентensureFullBody()подгружает перед просмотром/правкой/флешкартой (иначе правка сохранила бы усечённый текст). - ✅ Пагинация рендера: клиент держит весь список (поиск/фильтр/сортировка в памяти), но в DOM рисует
PAGE_SIZE=60карточек + «Показать ещё»;_shownсбрасывается на смену фильтра. Снимает стоимость рендера тысяч узлов, не ломая клиентский поиск (keyset на сервере не нужен на текущих объёмах). - ✅ Серверные миниатюры
board/image:uploadPersonalFile(sharp → webp ≤480px) возвращает{url, thumbUrl}; колонкаthumb_url(мигр. 074); грид рисует<img src=thumb_url||url>, просмотр/скачивание/аннотация — полныйurl. Чистится по ссылкам (releaseFileForUrl теперь матчит url и thumb_url); share копирует thumb; квота считает файл+миниатюру. Клиентские сейверы (board-clip/material-save/textbook-clip/draw) пробрасываютthumbUrl.
Фаза 3 — Доводка заложенных фич ✅
- ✅ UI тегов: ввод в модалках создания/правки + чипы на карточке (клик → фильтр) + пилюля активного фильтра.
- ✅ Ссылка «открыть исходный урок» на карточке (
/my-lessons?session=<id>, естьsource_session_id). - ✅ Цвет папки (палитра 8 пресетов, тинт иконки в рейле) + сортировка папок «Выше/Ниже» в модалке правки
(нормализует
sort_orderк индексам).safeColorгейтит inline-style инъекцию (только hex).
Фаза 4 — UX ✅
- ✅ Варианты сортировки (новые/старые/имя/тип) — селект в тулбаре.
- ✅ Множественный выбор (чекбокс на карточке) + панель массовых действий (переместить/удалить, reuse per-item API).
- ✅ Живое превью KaTeX в редакторе заметки (oninput →
mmPreview→mathHtml).
Статус — ПЛАН V2 ВЫПОЛНЕН
Ф1–Ф4 ✅. Backend: 19 тестов materials.test.js (CRUD/владелец/коллекции/share/URL-allowlist/квота/
ссылочная чистка url+thumb/round-trip thumb_url). Frontend: headless-смоук my-materials.html (синтаксис +
deep-link/теги/чекбокс/bulk/тинт папки + <img> на thumb_url + пагинация «Показать ещё»). sharp-пайплайн и
client-сейверы (board-clip/material-save/textbook-clip) проверены. Открытого из плана не осталось.
Порядок
Ф1 (этот заход) → Ф2 → Ф3 → Ф4. Ф1 — серверный фундамент (риск-возврат, без него фронт-фичи множат
мусор). Дальше преимущественно фронтенд my-materials.html + точечные ручки API.