mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Просмотр импортирует stairTargetState из stairs-editor-model.ts, и общий модуль Rollup кладёт в eager-чанк: новые функции протяжки, ресайза, магнита и диалога утянули туда 2,5 КБ gzip и выбили бюджет первого View. Теперь eager-модуль снова маленький, а всё редакторское живёт в stairs-box.ts, который импортирует только ленивый редактор: initial View 299 165 Б при бюджете 301 066, запас 1 901 Б. Потолок ленивого редактора 236 400 → 239 000 (замер 237 985) с записью истории в bundle-budget.mjs; база монолита bundleBytes поднята тем же коммитом (+10 069 Б сырых — код слоя редактирования). Хук data-hp получил запись stair-frame; смок не пишет в приватное поле. Issue: #676 User-Visible: no Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
131 lines
7.1 KiB
Markdown
131 lines
7.1 KiB
Markdown
# Stairs
|
||
|
||
Stairs are plan-level navigation objects. They show the physical place and
|
||
direction of an ascent on one space and can link that space to one other House
|
||
Plan space. The link is deliberately one-way: adding or editing a stair never
|
||
creates or changes anything on the target floor.
|
||
|
||
## User contract
|
||
|
||
The Plan editor has one **Stairs** group with **Straight** and **Spiral** tools.
|
||
Stairs are drawn like decor shapes (#676): press on the plan, drag and release.
|
||
The dominant drag axis is the rise axis, the ascent points from the press to
|
||
the release ("draw from the bottom up"), the sizes are the drawn extents (never
|
||
below one 30 cm tread) and the angle is 0°, 90°, 180° or 270°; equal extents
|
||
prefer the horizontal. A spiral stair takes the square of the drag, anchored at
|
||
the press. A drag shorter than one grid cell is a click and places the default
|
||
size (240 × 100 cm straight, R 90 cm spiral) at the press. The new stair stays
|
||
selected and the tool remains armed; `Esc` during the drag discards the draft.
|
||
|
||
The selected stair shows the same frame as furniture in the decor editor,
|
||
painted above wall bodies: a dashed outline, four corner and four side handles
|
||
(a spiral stair has four handles on its axis tangents), a stem with the
|
||
rotation handle above the local top side. Handle hit areas are finger-sized on
|
||
screen at every zoom. The cursor over a handle follows the handle's world
|
||
direction — `ew`/`ns` when it moves along an axis, a diagonal arrow otherwise —
|
||
and the rotation handle shows the circular cursor. Drag the body to move, a
|
||
handle to resize about the opposite side or corner (sizes stop at 30 cm and a
|
||
pointer dragged past the anchor never mirrors the stair; `Shift` keeps the
|
||
proportions), and the upper handle to rotate. Resizing never changes the angle.
|
||
Rotation is continuous; holding `Shift` snaps to the nearest multiple of 45
|
||
degrees. `Esc` cancels an active transform or clears the selection;
|
||
Delete/Backspace removes the selected stair; the ordinary Plan Undo/Redo
|
||
history covers create, edit, move, resize, rotate and delete. The click a
|
||
browser synthesizes after a gesture never reaches the plan tool: it places no
|
||
copy under Stairs and keeps the selection under Select. Under any other plan
|
||
tool the frame is not rendered at all.
|
||
|
||
Double click opens properties. A straight stair stores positive length and
|
||
width; a spiral stair stores a positive radius. The fields accept 30–10000 cm
|
||
(inches when Home Assistant is imperial); a field left untouched keeps the
|
||
stored number bit for bit, and saving without changes writes nothing. The
|
||
dialog also selects the rise direction, rotation and an optional target space.
|
||
A target cannot be the current space. A missing, self or deleted target leaves
|
||
the stair visible and editable but shows a repair warning and makes View
|
||
activation a no-op.
|
||
|
||
In View, hovering a stair with a mouse shows the card tooltip "Go to floor
|
||
<title>" when — and only when — the stair is a valid link (`active` target
|
||
state); a missing, self, deleted or fixed-floor target shows no tooltip.
|
||
|
||
In an ordinary multi-space card, a clean click/tap or keyboard activation on a
|
||
valid stair switches to the target tab and restores that floor's remembered
|
||
camera. Pan, pinch, long press, swipe and pointer cancellation do not navigate.
|
||
In a card configured with `floor`, stairs are visible but inert. The target
|
||
floor receives no automatic stair, highlight or camera centring.
|
||
|
||
## Geometry and drawing
|
||
|
||
Both variants use the same continuous transform contract as furniture: their
|
||
authored position, size and angle are not grid-quantised on save or by
|
||
**Optimize plans**. The wall magnet works per side (#676): a side of the box
|
||
(a tangent of the circle) that is parallel to a visible physical wall face
|
||
within 5° and within six grid cells of it lands flush on that face. While
|
||
moving, the nearest such side wins and a straight stair turns by at most 5° to
|
||
become exactly parallel — a stair standing end-on to a wall snaps with its end
|
||
and keeps its angle. While resizing or drawing, only the dragged sides snap
|
||
and the angle never changes. Room faces are one-sided; the exposed faces of
|
||
partitions and columns are derived from the body's winding, so a stair
|
||
straddling a thin wall never snaps to the face hidden inside the masonry. Any
|
||
straight/circular pair of stairs can snap footprint-to-footprint. The other
|
||
stair is never modified or linked by the magnet.
|
||
|
||
Straight tread lines are perpendicular to the rise axis and start at the lower
|
||
edge every 30 cm. A remainder shorter than 30 cm stays at the upper edge.
|
||
Spiral stairs make one full turn; radial treads use the same 30 cm physical step
|
||
measured on the travel line at two thirds of the radius. Their direction is
|
||
clockwise or counter-clockwise when viewed from above. The arrow always means
|
||
physical ascent, not the direction of navigation between named tabs.
|
||
|
||
The footprint overlap is removed from clean room floor area exactly once. It
|
||
does not cut the visible floor, change room or wall geometry, create a light
|
||
occluder, affect Glow/sun/vacuum, or participate in Optimize. In 2.5D the first
|
||
version remains a flat symbol projected with the floor/decor plane; it has no
|
||
height, risers, railings or shadows.
|
||
|
||
## Persisted model
|
||
|
||
Each space may contain `stairs: []`, bounded to 250 valid records:
|
||
|
||
```json
|
||
{
|
||
"id": "stair-main",
|
||
"kind": "straight",
|
||
"x": 0.42,
|
||
"y": 0.55,
|
||
"angle": 90,
|
||
"length": 0.24,
|
||
"width": 0.10,
|
||
"direction": "forward",
|
||
"target_space_id": "floor-2"
|
||
}
|
||
```
|
||
|
||
`kind:"spiral"` replaces `length`/`width` with `radius` and uses direction
|
||
`clockwise` or `counterclockwise`. Coordinates and sizes use the normalized
|
||
plan coordinate system; physical labels are derived through `cell_cm`.
|
||
`target_space_id` is nullable. Unknown sibling fields survive validation and
|
||
round trips for forward compatibility. Full backup/import/export, one-space
|
||
transfer, plan-only transfer, diagnostics and support packages preserve the
|
||
same records. When a complete backup removes a target space its incoming stair
|
||
links are cleared; a one-space transfer cannot invent an external target.
|
||
|
||
## Implementation boundary
|
||
|
||
- `src/stairs.ts` owns validation, drawing geometry and area-subtraction
|
||
primitives.
|
||
- `src/stairs-view.ts` is the eager read-only boundary for symbols, guarded
|
||
navigation and touch/pointer gesture suppression.
|
||
- `src/stairs-editor-model.ts` keeps the eager-safe helpers the View runtime
|
||
shares (defaults, kind conversion, target state, stair-to-stair magnet);
|
||
`src/stairs-box.ts` owns the editor-only oriented-box transforms —
|
||
drag-to-draw, resize about the anchor, edge magnets, handle bearings and
|
||
cursors, dialog conversion — and is imported only by the lazy editor chunk;
|
||
`src/stairs-editor.ts` owns the lazy Plan UI (gestures, the frame rendered
|
||
by the card in its top overlay, properties).
|
||
- `src/clean-floor.ts` and `src/summary-panel-metrics.ts` consume the same
|
||
footprint subtraction for room cards and summary totals; PDF rendering uses
|
||
the same stair outline/tread geometry without changing the room floor paint.
|
||
- Backend schemas and transfer/support surfaces treat `stairs` as a bounded,
|
||
additive space collection. Old configs without it remain unchanged.
|