26 KiB
#159 — Новый набор мебели и двухуровневая библиотека
Issue: #159
Источник набора: архив в комментарии, SHA-256 9E969016EE3B4B4E3DB776FEC53C8B387B91368B118EB5E39911483DEF1B0953.
Сценарий
Администратор дома в desktop-редакторе подложки открывает библиотеку мебели, сначала выбирает понятную категорию по фронтальной иконке, затем — конкретный вариант по виду сверху, задаёт его реальный размер и ставит на план. Домочадцы видят обновлённый линейный символ в обычном View и киоске без дополнительных действий и без изменения поведения плана.
Что человек увидит до и после
До изменения библиотека показывает 30 условных видов сверху одним длинным списком. После изменения она показывает компактный каталог категорий с новыми фронтальными иконками и второй экран с вариантами вида сверху; на план можно разместить 56 символов, а уже размещённая мебель с прежними ID автоматически получает новый рисунок там, где дизайнер передал замену.
Проблема
Текущая библиотека собрана из простых программных примитивов. В ней мало вариантов, а часть символов недостаточно похожа на реальный предмет. Плоский список плохо масштабируется: добавление 26 вариантов сделает его длиннее и не объяснит пользователю связь «Диваны → двухместный / трёхместный / угловой».
Полученный набор решает визуальную часть, но не может быть вставлен как
произвольный SVG из config: House Plan намеренно не принимает пользовательский
markup. Нужен детерминированный compile-time каталог безопасной path-геометрии,
который сохраняет текущий config-контракт decor[].symbol.
Проверенный вход и права
- Архив содержит 33 SVG категорий меню и 44 SVG вида сверху: 18
replace, 26add. - Технический валидатор архива проходит; все 77 SVG состоят только из
автономных
pathсfill="none",stroke="currentColor", без внешних ссылок, transforms, CSS, растра, текста, script/filter/mask/clipPath. - Владелец репозитория публично подтвердил,
что является автором всех 77 SVG и разрешает House Plan использовать,
изменять и распространять их под MIT License репозитория без обязательной
отдельной атрибуции в UI. В vendored-копию попадают
author: Matysh,license: MIT; исходныйTBDне переносится как релизная метаинформация. - Превью из архива — материал визуального ревью, не runtime-ресурс и не источник геометрии.
Scope
- Вендоринг нормализованного исходного набора в
assets/furniture/houseplan-0.3.0/: manifest, README/provenance, MIT license и 77 SVG. PNG-превью остаются в issue и не дублируются в git. - Детерминированный генератор, который валидирует разрешённый SVG-поднабор и
создаёт раздельные TS-каталоги:
- plan-art, нужный View и редактору;
- menu-art, импортируемый только ленивым editor graph.
- Итоговый каталог из 56 плановых символов: все 30 прежних ID плюс 26 новых.
- Замена рисунка и default-размера для 18 прежних ID из manifest без миграции сохранённой мебели.
- Двухуровневая палитра «категории → варианты» с фронтальными и top-view SVG.
- Полные EN/RU/DE названия категорий и вариантов.
- Обновление свойств мебели, тестов, golden/smoke, документации и changelog.
Не входит
- загрузка пользовательских SVG/паков и выбор пакета;
- удаление либо переименование любого существующего symbol ID;
- 3D/изометрическая мебель, заливки, цвета, тени и материалы;
- новые жесты, wall magnet, resize/rotate, z-order или collision detection;
- новые плановые предметы для четырёх menu-only исходников
computer,oven,hood,exercise; - изменение backend-схемы decor и миграция config;
- обещание удобного редактирования на touch: редактор остаётся desktop-first.
Каталог и совместимость
Замены существующих ID
coffee_table, table_dining, table_round, desk, chair, armchair,
sofa, bed_single, bed_double, nightstand, bookshelf, wardrobe,
stove, tv, toilet, bathtub, bidet, kitchen_sink.
Для нового размещения применяются размеры из manifest. Для уже сохранённой
мебели x/y/w/h/angle остаются байт-в-байт прежними; меняется только рисунок,
разрешаемый по тому же symbol.
Новые ID
coffee_table_round, coffee_table_oval, coffee_table_rounded,
table_dining_oval, table_dining_rounded, desk_corner, chair_bar,
armchair_office, sofa_three_seat, sofa_corner_right, cabinet_tv,
cabinet_shoe, cabinet_sink, wall_unit, kitchen_floor,
kitchen_floor_corner, kitchen_wall, kitchen_wall_corner, shelf_floor,
shelf_wall, cooktop_two, tv_wall, toilet_built_in, bathtub_corner,
bidet_built_in, kitchen_sink_double.
Неперерисованные прежние ID
fridge, dishwasher, washer, dryer, ac, water_heater, shower,
sink, stairs, fireplace, plant, rug сохраняют текущую геометрию,
размеры и поведение. Они получают фронтальные category icons из нового набора.
Категории
Каждый плановый символ имеет ровно одну категорию. Для неперерисованных ID:
| Symbol | Category |
|---|---|
ac |
air_conditioner |
water_heater |
boiler |
fridge |
fridge |
dishwasher |
dishwasher |
washer |
washer |
dryer |
dryer |
shower |
shower |
sink |
sink |
stairs |
stairs |
fireplace |
fireplace |
plant |
plant |
rug |
rug |
Остальные связи берутся из symbols[].menu_icon manifest. Категория без
единого top-view symbol не отображается. Поэтому computer, oven, hood,
exercise остаются в vendored source для будущего дополнения, но не создают
пустых или неработающих кнопок.
Контракт UX
Первый уровень: категории
- Открытие «Мебели» показывает четыре прежние группы в прежнем порядке: мебель, техника, сантехника, прочее.
- Внутри группы показывается одна плитка на непустую категорию: новая фронтальная иконка и локализованное имя.
- Плитка не начинает размещение и не меняет config. Она открывает второй уровень внутри той же context tray/palette.
- Закрытие палитры сбрасывает навигацию на первый уровень и возвращает инструмент в Select по существующему контракту.
Второй уровень: варианты
- Заголовок показывает кнопку «Назад», фронтальную иконку и имя категории.
- Все варианты категории показываются плитками с реальным видом сверху и локализованным названием. Даже категория с одним вариантом проходит через этот экран: одинаковый результат клика важнее экономии одного клика.
- Клик по варианту вооружает прежний stamp-flow, подсвечивает вариант и показывает прежние поля ширины/глубины и подсказку размещения.
- Выбор другого варианта сбрасывает width/depth на defaults нового варианта, как нынешний выбор другого symbol. Ручные размеры не переносятся между разными вариантами.
- «Назад» возвращает к категориям и снимает вооружённый symbol, чтобы нажатие по плану после смены экрана не поставило скрыто выбранный предмет.
- После успешной одиночной установки инструмент возвращается в Select, как сейчас. Многократный stamp не добавляется.
Свойства уже размещённого предмета
- Поле «Символ» остаётся одним select без дополнительного шага, но варианты группируются сначала по четырём product-группам, затем по category label.
- Текущий ID всегда выбран, в том числе для старого или нового символа.
- Смена symbol сохраняет текущую коробку, положение и поворот по действующему контракту; default-размер используется только при новом размещении.
Доступность и touch
- Категории/варианты — настоящие
buttonсtitle/accessible name; «Назад» доступен с клавиатуры и имеет локализованную подпись. - Палитра сохраняет собственный вертикальный/горизонтальный scroll и не увеличивает рабочую область редактора.
- Touch editor: best effort. Плитки не могут быть меньше действующих touch targets, а нажатие внутри tray не проходит на план. View/kiosk рендерят мебель полностью и являются release-blocking.
Рендер и безопасность
- Runtime не читает SVG-файлы и не принимает SVG/path из config. Генератор
извлекает только локальные
path[d]из проверенного vendored набора. - Разрешённый source-контракт: корректный XML; точный viewBox; только
svg/g/path;fill=none,stroke=currentColor; без URL, style/class, event attributes, transform, script, text, foreignObject, image, animation и SVG effects. Нарушение прерывает генерацию/тест, а не попадает в bundle. - Все path одного символа объединяются в одну неизменяемую
d-строку. Один размещённый предмет остаётся одним интерактивным<path>, включая erase-hit. - Source viewBox хранится рядом с
d. Рендер применяет translate/rotate и non-uniform scale из source box в сохранённыеw/h;vector-effectне даёт scale менять пользовательскую физическую толщину контура. - Plan path получает текущие
color,opacity,width_cm, round cap/join и все существующиеdata-hp/data-id/data-kind/data-symbolhooks. - Menu/variant preview рендерит тот же immutable path в своём viewBox с
currentColor, прозрачным фоном и фиксированным non-scaling stroke. Variant preview сохраняет реальные пропорции в 40×40, как текущая палитра. - Неизвестный
decor[].symbolпо-прежнему валиден для backend, не падает и ничего не рисует в старой карточке.
Модель данных и миграция
Persisted schema не меняется:
{ kind: 'furniture', symbol: string, x, y, w, h, angle?, ...style }
- Новых config-полей, schema/model version и migration нет.
operation, category ID, source viewBox и SVG path — compile-time metadata, не пользовательские данные.- Сохранение старого плана не переписывает symbol или геометрию мебели.
- Backend продолжает валидировать только безопасный формат ID, а не закрытый список символов — forward compatibility сохраняется.
Генерация и source of truth
assets/furniture/houseplan-0.3.0/manifest.json— список дизайнерских замен/добавлений и menu mapping.- README внутри каталога фиксирует SHA-256 исходного архива, URL issue/Figma,
авторство и отличия нормализованной копии (
author/license/pack_id). scripts/generate-furniture-assets.mjsвалидирует пакет и атомарно генерирует два стабильных файла без timestamp/абсолютных путей:src/furniture-plan-art.generated.tsиsrc/furniture-menu-art.generated.ts.--checkпересобирает в памяти и падает при stale generated output. Он входит в unit/CI-проверку задачи.- Plan-art импортируется
src/furniture.ts, потому что нужен обычному View. Menu-art импортируется толькоsrc/houseplan-editor-runtime.ts, чтобы 33 фронтальные иконки не попали в initial View graph.
i18n
Меняются src/i18n/en.json, ru.json, de.json:
furn.category_<id>для каждой отображаемой категории;furn.sym_<id>для всех 26 новых ID;- существующие 18
furn.sym_*получают уточнённые названия manifest; furn.back_to_categoriesи accessible label для возврата, если общий ключ Back не подходит грамматически.
RU/EN берутся из manifest и редакционно нормализуются под текущий UI. DE переводится в том же изменении; fallback на английский для нового каталога не допускается.
Производительность и bundle
- На один предмет остаётся один plan
<path>; DOM-сложность View не растёт от числа исходных subpaths. - Категории/варианты создаются только при открытом editor palette.
npm run bundle:budgetобязан пройти общий initial View ceiling.- В handoff записываются gzip-дельты initial View и lazy editor относительно
origin/dev. Front-menu art в initial View graph — блокирующая ошибка. - Дополнительный initial View gzip для 44 plan SVG — не более 18 KiB. Это блокирующий критерий задачи: при превышении реализация упрощает представление и повторяет замер. Поднять общий budget или принять превышение в #159 нельзя; иное решение требует отдельного изменения ТЗ и нового ревью до реализации.
Критерии приёмки
AC1 — целостность и provenance пакета (unit, ревью кода)
В issue есть публичное подтверждение владельца об авторстве всех 77 SVG и
разрешении использовать, изменять и распространять их под MIT License
репозитория без отдельной UI-атрибуции. Vendored source соответствует
зафиксированному архиву по всем 77 SVG; нормализованы только подтверждённые
метаданные author/license/pack ID и документация. Генератор отклоняет
запрещённый SVG и --check подтверждает актуальность двух generated каталогов.
AC2 — полная совместимость ID (unit)
Итоговый каталог содержит ровно 56 уникальных ID: прежние 30 без удаления или переименования и новые 26. 18 replacement-ID используют новый art/defaults, 12 retained-ID — прежний art/defaults. Unknown ID остаётся безопасным no-op.
AC3 — категории и варианты (unit, smoke)
В первом уровне есть только непустые категории с фронтальными иконками; четыре menu-only категории скрыты. Переход во второй уровень, Back, выбор каждого варианта, selected state, reset defaults и однократный stamp выполняют UX-контракт.
AC4 — плановый рендер (unit, smoke, golden)
Новый и replacement symbol одинаково рисуются в Background editor, View, light/dark theme и при непропорциональном resize; stroke остаётся одинаковой толщины, цвет/opacity/rotation/hooks/erase-hit не регрессируют. Сохранённый replacement-предмет сохраняет свою коробку и получает новый рисунок.
AC5 — свойства и round-trip (unit, smoke)
Properties select показывает/выбирает все 56 ID; смена symbol сохраняет геометрию, save/reload не меняет ID, а backend принимает новые IDs без обновления allow-list.
AC6 — локализации (unit, smoke)
Все видимые category/symbol labels существуют и непусты в EN/RU/DE; ни одна плитка не показывает ключ или английский fallback в RU/DE.
AC7 — editor/touch safety (smoke, ревью кода)
Pointer внутри обоих уровней палитры не ставит мебель на план и не начинает pan; закрытие/Back не оставляют скрытый armed stamp. View и kiosk не получают новых handlers и рендерят те же symbol paths.
AC8 — bundle и DOM budget (build, unit)
Budget проходит; initial View gzip delta ≤18 KiB, menu-art отсутствует в initial graph; один мебельный объект создаёт один основной plan path и один erase-hit только в активном erase-mode.
AC9 — документация и релиз (review)
Обновлены docs/FURNITURE.md, релевантный user guide/docs/STATUS.md,
docs/CHANGELOG.md, docs/CHANGELOG.ru.md; provenance и desktop-first правило
зафиксированы. Golden candidates приняты только из Linux CI по штатному процессу.
План автотестов
- Расширить
test/furniture.test.mjs: множества ID, category mapping, replacements/retained, source viewBox/path, finite transform, unknown ID, default sizes и one-path contract. - Новый тест generator/integrity: manifest↔SVG↔generated parity, запрет каждого опасного SVG-класса, orphan menu policy, deterministic output.
- Расширить
demo/smoke_furniture.mjs: category → variants → select → size → place, Back/close safety, properties change, reload, старый/new symbol. - Golden matrix: category screen light, category screen dark, variant screen с несколькими формами и plan scene с replacement + retained + new furniture.
- i18n parity tests для всех новых ключей EN/RU/DE.
- Build manifest comparison с
origin/devиnpm run bundle:budget.
Release-артефакты
docs/CHANGELOG.mdиdocs/CHANGELOG.ru.md: значимое пользовательское изменение со ссылкой на #159;docs/FURNITURE.md: каталог, двухуровневый выбор, размеры/совместимость, provenance;- user guide/STATUS — только разделы, где перечисляется библиотека мебели;
- обновлённые Linux golden baselines и запись визуального review;
- новые docs screenshots — только если изменяемый сценарий входит в их matrix;
- bundle-size delta initial/lazy в handoff и отчёте code review;
- security artifact отдельно не нужен: безопасность доказывает allow-list generator и отсутствие runtime SVG input.
Риски и митигации
| Риск | Митигация |
|---|---|
| Новый рисунок неожиданно меняет старый план | replacement только по явно переданным ID; геометрия config не меняется; golden before/after |
| Menu art раздует View bundle | отдельный lazy import и graph assertion |
| SVG path меняет толщину при resize | vector-effect: non-scaling-stroke, экстремальные aspect-ratio tests |
| Пустая category создаёт dead end | category выводится только при ≥1 plan symbol; invariant test |
| Новый symbol ломает backend | backend сохраняет regex-only forward-compatible validation; round-trip smoke |
| Длинные переводы ломают tray | RU/EN/DE golden и внутренний scroll без изменения stage |
| Небезопасный SVG попадёт в runtime | strict compile-time validator; в config нет markup/path |
Откат
Кодовый откат возвращает прежний каталог и палитру. Persisted schema не изменялась: старые 30 ID снова рисуются прежними символами. Новые 26 ID в старой версии остаются валидными данными backend и безопасно не рисуются; после возврата новой версии появляются снова с сохранённой геометрией. Удалять или переписывать такие records при откате запрещено.
Продуктово-правовое решение
Авторство и MIT-разрешение подтверждены владельцем в issue comment. Открытых продуктовых или правовых вопросов нет.
Принято предположительно, поменять свободно на ревью
- Generated TS, а не runtime SVG imports, выбран ради CSP/HACS и контроля bundle.
- Четыре menu-only SVG вендорятся для полноты авторского исходника, но скрыты до появления top-view counterparts.
- Все категории, включая одиночные, используют одинаковый второй уровень; это осознанно добавляет один клик ради предсказуемого интерфейса.
- Исходные PNG-превью не дублируются в git: issue остаётся immutable source, а product visuals защищает штатная golden matrix.