Files
houseplan-card/docs/FURNITURE.md
T
Claude 4bc3e9baa6 docs(hygiene): ARCHITECTURE.md — карта, а не хроника (#680)
Волна 3 эпика #674. ARCHITECTURE.md 2330 → 653 строки: система координат
прототипа 1489×1053, разделы Hidden Isometric Stage 2/4 (текущее — ISOMETRIC.md,
история — ADR 122/570), хроники «Additions v1.28–v1.41» и «Audit follow-ups»
сняты; подсистемы — короткое описание со ссылкой на канонический документ.
Устаревшее исправлено по коду: дерево Layout (store.py, frontend_registration,
весь список модулей), хранилища (config, layout, virtual_lights, trails),
ленивые локали de и fr, таблица WS API (31 + 3 команды, #256-проекция,
space/delete, files/cleanup без keep, контент через /api/houseplan/content),
DevItem без несуществующих полей, отказ help/feedback по support_api.

Всё ещё верное и не записанное в другом месте перенесено в канонические
документы: DEVICE-PRESENTATION (заметки реализации, Action authority, черновик
диалога), RADAR (карта реализации), VACUUM (владение кодом), CANVAS (icon_size,
--hp-cell-visual-scale, барьер записи координат), FILTERING (#44, каталог),
DECOR-EDITOR §7 (инварианты бэкенда ассетов), WALL-THICKNESS (§1 идентичность
и нулевые стены, §2 кэши, §4 hover/туннели/острова, §6 удаление комнаты,
§9 failed-core, §11 завершение цепочки и комната по грани), ISOMETRIC
(isoPlaneMatrix, iso-overlays, створки, служебные атрибуты), LIGHT (Glow над
заливкой #55, формула и screen-смешение), CONFIG-COMPATIBILITY (манифест схемы
#33, Masonry-слоты #561, квадратный холст v1.48, устаревшие URL контента,
layout/set #356), SCOPE (таблица владельцев при сборке файлов), WARM-REMOUNT
§5 (холодная загрузка и визуальная непрерывность), UX-MODES (порядок
стартового пространства), PDF-EXPORT (граница реализации), FURNITURE (adopt,
BOOT_MAX_MS). Раздел TESTING «Backend quality gates (#42)» и строка
DEVELOPMENT о bundle-freshness — из того же разбора, в предыдущем коммите.

Issue: #680
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-27 22:33:04 +03:00

170 lines
9.1 KiB
Markdown

# Furniture library
Status: **implemented and expanded by issues #159 and #383**. The Background editor
stores furniture as ordinary decor:
```text
{ kind: "furniture", symbol, x, y, w, h, color, opacity, width_cm, angle?, flip_h?, flip_v? }
```
`w` and `h` are always positive physical extents. Optional boolean `flip_h`
and `flip_v` mirror the drawing inside that unchanged box; absent flags are
the original orientation. Existing furniture therefore keeps its position,
size, rotation, orientation and styling after an update.
## Library and source
The public library contains **60** top-view symbols, all of them designer
artwork from the corrected `assets/furniture/houseplan-0.4.1/svg/plan` (#593,
#606). The previous split — 44 designer symbols plus 12 built-ins drawn from
unit-box primitives — is gone: pack 0.4.0 redrew those 12 ids (refrigerator,
dishwasher, washer, dryer, air conditioner, water heater, shower, sink, stairs,
fireplace, plant and rug) and added `computer`, `hood`, `oven` and `cactus`.
Pack 0.4.1 corrects that last public id to `exercise` while retaining the
original drawing and a read-only alias for saved `cactus` objects.
The designer pack also contains 33 front-view category illustrations in
`svg/menu`. All 33 categories are populated. Pack 0.4.0 mistakenly labeled
an exercise-machine drawing as `cactus` and put it under `plant`; 0.4.1 keeps
that drawing unchanged under public id `exercise` in the `exercise` category.
The `bookshelf` and `shelf_floor` plan drawings were also exchanged in 0.4.1
to match their names. Menu artwork is unchanged. A saved `cactus` continues to
render and edit as an exercise machine without rewriting the stored id; an
explicit new selection writes `exercise`.
`pack.json` is the source of truth for stable ids, categories, names,
default centimetre dimensions and SVG paths. Run:
```powershell
npm run furniture:generate
npm run furniture:check
```
The generator validates a deliberately small, inert SVG subset and produces:
- `src/furniture-plan-catalog.generated.ts` — ids, groups, categories and
default sizes; the only part of the pack in the initial View graph;
- `src/furniture-plan-art.generated.ts` — the 60 top-view drawings, a lazy
chunk (#474) loaded by `src/furniture-art-runtime.ts` when a plan draws
furniture and imported statically by the editor;
- `src/furniture-menu-art.generated.ts` for the lazy editor graph only.
Generated files are never edited manually. The 93 drawings were created by
Sergey Matyunin (`Matysh`) and granted to the project under its MIT License in
[issue #593](https://github.com/Matysh/houseplan-card/issues/593#issuecomment-5739841899).
No separate attribution is required in the interface. The 0.4.1 pack's own
`README.md` records the reviewed 0.4.0 source archive, its SHA-256 and the
precise corrections; the historical 0.4.0 README remains unchanged.
## Palette interaction
Furniture is a two-level non-modal palette in the editor context tray:
1. The first level shows front-view **categories**, grouped as Furniture,
Appliances, Plumbing and Other.
2. Every category opens a second level, including categories that have only
one variant.
3. The second level shows the real top-view drawings. Back returns to the
category list and clears any armed symbol.
4. Picking a variant arms one placement and reveals editable Width and Depth
in the Home Assistant length unit. With a mouse, moving over the plan shows
the real top-view symbol at its exact future size, wall magnet, rotation and
canvas clamp. Editing Width or Depth updates that ghost immediately without
requiring another pointer move.
5. Clicking the plan places exactly the previewed object and returns to Select.
Shift keeps the established free-placement behaviour. The ghost is a
transient, 55%-opaque rendering aid: it is never saved and never enters
Undo history.
6. Pointer leave, Escape, palette/tool/editor/space changes and remount clear
the ghost. Unknown or stale symbol ids show and save nothing.
Touch and pen do not emulate hover or show a placement ghost. Their best-effort
editor path saves one object only after a clean tap; movement, pointer cancel
or a second contact cancels that pending stamp so pinch/cancel cannot create
furniture accidentally. View and kiosk touch guarantees remain unchanged.
The properties dialog remains a flat native select, grouped by category. Its
optgroup label includes both the parent group and category because HTML selects
cannot nest optgroups.
## Transform interaction
Selected furniture has four corner and four middle-edge resize handles. Corner
resize is continuous and preserves the original aspect ratio; `Shift` lets
width and depth change independently. A middle handle changes only its local
axis, including on a rotated object. Dragging any handle across the fixed
opposite edge keeps the gesture alive and mirrors the corresponding axis.
Rotation is continuous normally and snaps to the nearest multiple of 45° while
`Shift` is held. The rotation handle uses a circular-arrow cursor. These rules
are furniture-only: other decor and the backdrop keep their existing grid and
modifier contracts.
Properties show signed width/depth. A negative width is horizontal mirror and
a negative depth is vertical mirror; the two adjacent checkboxes are the same
state expressed explicitly. Save stores the absolute extents plus optional
flags. Zero is not a valid saved dimension.
## Rendering contract
Each object is still one visible `<path>` and one erase hit path. In Select,
an additional invisible path follows the same artwork and extends the target
10 physical centimetres beyond each visible stroke edge; empty areas of the
bounding box remain non-interactive. Designer artwork
keeps its native SVG `viewBox`; the renderer applies the user's stored width
and depth with a non-uniform transform. `vector-effect="non-scaling-stroke"`
rejects only that local width/depth distortion in the visible result; the
renderer separately applies the outer plan viewBox scale to the stroke width.
Consequently:
- resizing changes the physical object box without distorting line weight;
- camera zoom changes the visible line weight exactly like other physical
decor with the same `width_cm`;
- the user's decor colour, opacity and physical line width remain authoritative;
- `data-hp="decor"`, `data-kind="furniture"`, `data-id` and `data-symbol`
remain stable for card-mod;
- an id unknown to an older card remains valid data and simply renders
nothing instead of breaking the plan.
The top edge of every drawing is BACK (`y = 0`). Placement and dragging put
BACK on a **physical surface** of a wall, including the local atomic
half-thickness; the invisible centreline is not the contact surface. An outer
wall exposes both its room-facing and exterior surfaces, so raw pointer intent
keeps furniture inside or outside instead of pulling it through the masonry.
On a shared wall the raw pointer side selects the room. A new placement exactly
on an outer-wall axis defaults inside, while an exact-axis drag keeps the
piece's current side. The magnet reach is measured from the selected physical
surface. `Shift` keeps free placement and bypasses the wall magnet. Preview,
commit and drag share the same surface resolver. Furniture is decor:
it has no entity, state, room aggregation, collision model or automatic
binding to later wall edits, and already saved coordinates are never migrated.
## Compatibility and performance
The backend schema additively accepts optional boolean furniture `flip_h` and
`flip_v`; no configuration version or migration is needed. Every public id that
existed before #593 still exists and keeps its default centimetre dimensions to
the number: pack 0.4.0 changes the drawing inside the box, never the box. An
already saved object's `w` and `h` are never rewritten, and an id unknown to an
older card remains valid data that simply renders nothing.
Pack 0.4.1 reads legacy `symbol: cactus` as `exercise` in the regular plan,
editor preview/properties and PDF path. The alias is read-only: ordinary saves
leave the stored id and all physical/style fields alone. An explicit variant
change stores the new id. Downgrading to a pre-0.4.1 card does not draw a newly
placed `exercise`, but does not discard its saved record.
Plan artwork is available in View and kiosk: a plan with furniture requests the
artwork chunk once per page before its first frame, and the first-open veil
waits for it; a plan without furniture never requests it. If the chunk cannot
be loaded, **nothing** is drawn until the page reloads, and a toast says so
once. Before #593 the 12 primitive symbols drew regardless; that promise was
withdrawn deliberately when they became designer artwork, so the failure now
behaves the same way for all 60 pieces instead of for 48 of them. Front-view
menu art is imported only after the editor runtime is requested. The editor
hands its statically imported drawings over synchronously (`adopt`), a chunk
from another build counts as a failed load, and the boot veil waits for the
artwork only up to the card's `BOOT_MAX_MS` cap. Touch View/kiosk support is
blocking; editor ergonomics on touch remain best effort under
`docs/TOUCH-SUPPORT.md`.