mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
fix: settle editor chrome and opening render regressions
This commit is contained in:
@@ -8,6 +8,23 @@ House Plan is one HACS package with two parts plus a demo harness:
|
||||
|
||||
Standard commands live in `package.json` scripts, `CONTRIBUTING.md`, and `docs/DEVELOPMENT.md`. Read `docs/ARCHITECTURE.md` and `docs/STATUS.md` before non-trivial changes.
|
||||
|
||||
## Canonical backlog
|
||||
|
||||
GitHub is the only active backlog for House Plan:
|
||||
|
||||
- [GitHub Issues](https://github.com/Matysh/houseplan-card/issues) are the
|
||||
canonical task records: problem, scope, acceptance criteria and discussion.
|
||||
- [GitHub Projects (v2)](https://github.com/users/Matysh/projects/1) is
|
||||
the canonical prioritization and workflow-status view. Every open in-scope
|
||||
issue must be present there.
|
||||
|
||||
Before starting planned work, find or create its issue and keep its description,
|
||||
labels and Project status current as decisions and implementation state change.
|
||||
Close an issue only after the result is verified. Specs, audits and ADRs may
|
||||
remain under `docs/`, but must link to their issue and must not become a parallel
|
||||
task list. When repository documentation disagrees with Issues or Project v2,
|
||||
the GitHub backlog wins.
|
||||
|
||||
## Cursor Cloud specific instructions
|
||||
|
||||
The startup update script already runs `npm ci`, provisions a Python 3.13 backend venv at `.venv-backend`, and installs Playwright Chromium. You do not need to reinstall dependencies.
|
||||
|
||||
@@ -16,6 +16,16 @@ writing code? The **[Telegram chat @ha_houseplan](https://t.me/ha_houseplan)**
|
||||
is the quickest route to the author and other users. Bugs and concrete feature
|
||||
requests still belong in [issues](https://github.com/Matysh/houseplan-card/issues).
|
||||
|
||||
## Backlog and work status
|
||||
|
||||
[GitHub Issues](https://github.com/Matysh/houseplan-card/issues) and the linked
|
||||
[GitHub Project v2](https://github.com/users/Matysh/projects/1) are the
|
||||
only active project backlog. Issues own scope and acceptance criteria; Project
|
||||
v2 owns prioritization and workflow status. Before starting planned work, link
|
||||
it to an existing issue or create one, add it to the Project, and keep both
|
||||
surfaces current until the verified result is closed. Design specs and ADRs may
|
||||
support an issue, but they do not replace it or maintain a separate checklist.
|
||||
|
||||
## Five-minute setup
|
||||
|
||||
```bash
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
[](https://demo.houseplan.tech)
|
||||
[](https://t.me/ha_houseplan)
|
||||
|
||||
📘 **[Полное руководство пользователя](docs/USER-GUIDE.ru.md)** · **[Продуктовый аудит и план улучшений](docs/PRODUCT-IMPROVEMENT-PLAN.ru.md)**
|
||||
📘 **[Полное руководство пользователя](docs/USER-GUIDE.ru.md)** · 🗂 **[Беклог проекта](https://github.com/users/Matysh/projects/1)**
|
||||
|
||||
**Превратите Home Assistant в живую интерактивную карту дома.** Загрузите или
|
||||
нарисуйте план этажа, обведите комнаты мышкой — и умные устройства появятся на
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,9 @@
|
||||
|
||||
This layer catches visual regressions that DOM smokes cannot: wall seams and
|
||||
end caps, thick opening tunnels, Glow/sun clipping, hover contours, editor
|
||||
chrome, long dialog titles/footers, mobile clipping, themes and zoom/remount.
|
||||
chrome, the open contextual tray at wide/medium/narrow widths in English and
|
||||
Russian (selection, tool options, group and palette), long dialog
|
||||
titles/footers, mobile clipping, themes and zoom/remount.
|
||||
|
||||
## Safety contract
|
||||
|
||||
|
||||
@@ -143,6 +143,55 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
}
|
||||
if (scenario.editorTray) {
|
||||
let expectedKind = '';
|
||||
if (scenario.editorTray === 'plan-selection') {
|
||||
card._physicalSel = { kind: 'partition', id: 'geo-partition-h' };
|
||||
expectedKind = 'selection';
|
||||
} else if (scenario.editorTray === 'plan-tool') {
|
||||
card._physicalSel = null;
|
||||
card._tool = 'draw';
|
||||
expectedKind = 'tool';
|
||||
} else if (scenario.editorTray === 'decor-selection') {
|
||||
card._decorTool = 'select';
|
||||
card._decorSel = 'geo-axis-h';
|
||||
expectedKind = 'selection';
|
||||
} else if (scenario.editorTray === 'decor-tool') {
|
||||
card._decorSel = null;
|
||||
card._decorTool = 'line';
|
||||
expectedKind = 'tool';
|
||||
} else if (scenario.editorTray === 'furniture-palette') {
|
||||
card._decorSel = null;
|
||||
card._furnPalette = null;
|
||||
card._editorSecondary.openPalette();
|
||||
card._decorTool = 'furniture';
|
||||
expectedKind = 'palette';
|
||||
} else if (scenario.editorTray === 'group') {
|
||||
const group = {
|
||||
id: 'golden-group', label: 'Arrange', icon: 'mdi:shape-outline', items: [
|
||||
{ id: 'align', label: 'Align', icon: 'mdi:format-align-center', role: 'command', invoke: () => undefined },
|
||||
{ id: 'distribute', label: 'Distribute', icon: 'mdi:format-horizontal-align-center', role: 'command', invoke: () => undefined },
|
||||
],
|
||||
};
|
||||
Object.defineProperty(card, '_editorToolbarGroups', {
|
||||
configurable: true,
|
||||
get: () => [group],
|
||||
});
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
card._editorSecondary.toggleGroup(card._editorToolbarGroups, group.id);
|
||||
expectedKind = 'group';
|
||||
} else {
|
||||
throw new Error(`unknown golden editor tray: ${scenario.editorTray}`);
|
||||
}
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
await frame();
|
||||
const tray = card.renderRoot.querySelector(
|
||||
`.editor-secondary-host.open .editor-secondary.kind-${expectedKind}`,
|
||||
);
|
||||
if (!tray) throw new Error(`golden editor tray did not open: ${scenario.editorTray}`);
|
||||
}
|
||||
if (scenario.hoverRoom) {
|
||||
const room = card._spaceModel().rooms.find((item) => item.id === scenario.hoverRoom);
|
||||
if (!room) throw new Error(`golden hover room missing: ${scenario.hoverRoom}`);
|
||||
@@ -185,6 +234,8 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
mode: card._mode,
|
||||
devices: card._devices.length,
|
||||
dialog: !!card.renderRoot.querySelector('hp-dialog'),
|
||||
editorTray: card.renderRoot.querySelector('.editor-secondary-host.open .editor-secondary')
|
||||
?.className || '',
|
||||
};
|
||||
}, { fixture, scenario });
|
||||
}
|
||||
|
||||
+21
-1
@@ -1,5 +1,5 @@
|
||||
/** Data-only HP-QA-01 capture matrix. Bump when framing or scenarios change. */
|
||||
export const GOLDEN_MATRIX_VERSION = 2;
|
||||
export const GOLDEN_MATRIX_VERSION = 3;
|
||||
|
||||
const stage = { capture: 'stage', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0005 } };
|
||||
const page = { capture: 'page', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0008 } };
|
||||
@@ -15,6 +15,26 @@ export const GOLDEN_SCENARIOS = Object.freeze([
|
||||
theme: 'dark', viewport: { width: 1180, height: 900 }, ...page },
|
||||
{ id: 'geometry-decor-editor-dark', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
|
||||
theme: 'dark', viewport: { width: 1180, height: 900 }, ...page },
|
||||
// HP-UX-11 visual contract: every adaptive width is captured in both
|
||||
// languages while the six materially different tray contents are open.
|
||||
{ id: 'tray-wide-selection-en', fixture: 'visual', space: 'golden-geometry', mode: 'plan',
|
||||
editorTray: 'plan-selection', language: 'en', theme: 'dark',
|
||||
viewport: { width: 1180, height: 900 }, ...page },
|
||||
{ id: 'tray-wide-tool-ru', fixture: 'visual', space: 'golden-geometry', mode: 'plan',
|
||||
editorTray: 'plan-tool', language: 'ru', theme: 'dark',
|
||||
viewport: { width: 1180, height: 900 }, ...page },
|
||||
{ id: 'tray-medium-group-en', fixture: 'visual', space: 'golden-geometry', mode: 'plan',
|
||||
editorTray: 'group', language: 'en', theme: 'dark',
|
||||
viewport: { width: 760, height: 820 }, ...page },
|
||||
{ id: 'tray-medium-selection-ru', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
|
||||
editorTray: 'decor-selection', language: 'ru', theme: 'dark',
|
||||
viewport: { width: 760, height: 820 }, ...page },
|
||||
{ id: 'tray-narrow-palette-en', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
|
||||
editorTray: 'furniture-palette', language: 'en', theme: 'dark',
|
||||
viewport: { width: 390, height: 760 }, ...page },
|
||||
{ id: 'tray-narrow-tool-ru', fixture: 'visual', space: 'golden-geometry', mode: 'decor',
|
||||
editorTray: 'decor-tool', language: 'ru', theme: 'dark',
|
||||
viewport: { width: 390, height: 760 }, ...page },
|
||||
{ id: 'geometry-diagonal-45-opening-dark', fixture: 'visual', space: 'golden-geometry', mode: 'view',
|
||||
theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage },
|
||||
{ id: 'openings-thick-wall-dark', fixture: 'visual', space: 'golden-lighting', mode: 'view',
|
||||
|
||||
@@ -15,6 +15,15 @@ const res = await page.evaluate(async () => {
|
||||
out.planActive = c._mode === 'plan' && tabs()[0].classList.contains('active');
|
||||
out.planBar = !!sr().querySelector('.editbar');
|
||||
out.planBarClose = !!sr().querySelector('.editbar .barclose');
|
||||
const barClose = sr().querySelector('.editbar .barclose');
|
||||
const barCloseIcon = barClose?.querySelector('ha-icon');
|
||||
const barCloseRect = barClose?.getBoundingClientRect();
|
||||
const barCloseIconRect = barCloseIcon?.getBoundingClientRect();
|
||||
out.planBarCloseIconIsCentered = !!barCloseRect && !!barCloseIconRect
|
||||
&& Math.abs((barCloseIconRect.left + barCloseIconRect.width / 2)
|
||||
- (barCloseRect.left + barCloseRect.width / 2)) <= 1
|
||||
&& Math.abs((barCloseIconRect.top + barCloseIconRect.height / 2)
|
||||
- (barCloseRect.top + barCloseRect.height / 2)) <= 1;
|
||||
out.tabCross = !!tabs()[0].querySelector('.closex');
|
||||
// 3) повторный клик по активной вкладке — ничего
|
||||
tabs()[0].click(); await c.updateComplete;
|
||||
|
||||
@@ -72,6 +72,14 @@ const res = await page.evaluate(async () => {
|
||||
out.washerIsInThePalette = !!item('washer');
|
||||
const prevD = item('sofa')?.querySelector('svg.furnprev path')?.getAttribute('d') || '';
|
||||
out.previewIsDrawn = prevD.length > 10;
|
||||
const previewEl = item('sofa')?.querySelector('svg.furnprev');
|
||||
const previewRect = previewEl?.getBoundingClientRect();
|
||||
const itemRect = item('sofa')?.getBoundingClientRect();
|
||||
out.previewStaysInsideItsButton = !!previewRect && !!itemRect
|
||||
&& getComputedStyle(previewEl).position !== 'absolute'
|
||||
&& previewRect.width <= 42 && previewRect.height <= 42
|
||||
&& previewRect.left >= itemRect.left - 1 && previewRect.right <= itemRect.right + 1
|
||||
&& previewRect.top >= itemRect.top - 1 && previewRect.bottom <= itemRect.bottom + 1;
|
||||
out.namesAreLocalised = /\S/.test(item('sofa')?.querySelector('span')?.textContent || '');
|
||||
|
||||
// An unarmed explicit palette closes on the first press anywhere outside
|
||||
@@ -287,6 +295,8 @@ const res = await page.evaluate(async () => {
|
||||
ev('pointerdown', stageEl(), 320, 320); // символ НЕ выбран
|
||||
await c.updateComplete;
|
||||
out.noSymbolNoStamp = c._decorList.length === n0;
|
||||
out.unarmedCanvasDismissReturnsToSelect = c._decorTool === 'select' && !pal();
|
||||
out.unarmedCanvasDismissLeavesNoClickSuppression = c._suppressClick === false;
|
||||
c._decorTool = 'erase'; await c.updateComplete;
|
||||
ev('pointerdown', el(sofaId), 300, 300);
|
||||
await c.updateComplete;
|
||||
|
||||
+239
-227
File diff suppressed because one or more lines are too long
Vendored
+239
-227
File diff suppressed because one or more lines are too long
+10
-4
@@ -199,6 +199,14 @@ cancelled or one-hour-timed-out Validate withholds the asset. Bump the version
|
||||
everywhere in sync: `src/houseplan-card.ts` (CARD_VERSION), `package.json`,
|
||||
`custom_components/houseplan/manifest.json`, `custom_components/houseplan/const.py`.
|
||||
|
||||
Validate intentionally runs on branch pushes, not tag pushes, so an annotated
|
||||
release tag does not duplicate the expensive browser/performance matrix. Every
|
||||
tagged SHA must therefore already be pushed to a branch and have a completed
|
||||
green exact-SHA Validate run. For an owner-approved emergency hotfix, push a
|
||||
temporary `hotfix/*` branch and wait for Validate before creating the tag;
|
||||
never tag a detached or otherwise unpushed commit, because the release gate
|
||||
will wait for a run that cannot exist and then fail closed after one hour.
|
||||
|
||||
The GitHub Release body is a concise **bilingual user summary**, not a copy of
|
||||
the exhaustive changelog. Put Russian first and English second, with equivalent
|
||||
meaning in both sections. Give separate bullets only to significant features
|
||||
@@ -211,13 +219,11 @@ bodies of skipped releases. The full detail remains in both
|
||||
two explicit links, one to each language version of the changelog.
|
||||
|
||||
```md
|
||||
## Русский
|
||||
## Основное
|
||||
- Значимое изменение.
|
||||
- Мелкие исправления и улучшения.
|
||||
|
||||
---
|
||||
|
||||
## English
|
||||
## Highlights
|
||||
- Significant change.
|
||||
- Small fixes and improvements.
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|---|---|---|
|
||||
| [Полное руководство пользователя](USER-GUIDE.ru.md) | Пользователи и администраторы Home Assistant | Установка, первая настройка, пространства, комнаты, стены, проёмы, устройства, визуальные состояния, заливки, подложка, солнце, пылесосы, киоск, обслуживание и диагностика |
|
||||
| [Редактор подложки и декора](DECOR-EDITOR.md) | Пользователи, тестировщики и разработчики | Инструменты, единое выделение и трансформации, физические размеры, магнит, Undo/Redo, поведение картинки-подложки и совместимость старых полей |
|
||||
| [Продуктовый аудит и план улучшений](PRODUCT-IMPROVEMENT-PLAN.ru.md) | Владелец и разработчики продукта | Нелогичные, дублирующие и устаревшие функции, UX-риски, технический долг и поэтапный план рефакторинга |
|
||||
| [GitHub Issues](https://github.com/Matysh/houseplan-card/issues) и [Project v2](https://github.com/users/Matysh/projects/1) | Владелец, разработчики и контрибьюторы | Единственный актуальный backlog: задачи, приоритеты, решения и статусы выполнения |
|
||||
|
||||
Старые тематические документы в этом каталоге остаются инженерными спецификациями и историей решений. При расхождении пользовательского описания с интерфейсом текущей версии приоритет имеет новое руководство, а при расхождении с фактическим поведением — код текущей версии.
|
||||
|
||||
|
||||
+6
-3
@@ -3,9 +3,12 @@
|
||||
Goal: a **proper Home Assistant component** built to HA developer-docs patterns —
|
||||
not a hardcoded feature. Universal (any house, any language, any plan format),
|
||||
flexible (options instead of assumptions), and measured against the official
|
||||
**Integration Quality Scale** even though custom integrations are not formally graded
|
||||
(see `docs/PRODUCT-IMPROVEMENT-PLAN.ru.md` for the current product plan; the
|
||||
original market rationale is archived at `legacy/docs/PRODUCT-2026-07-05.md`).
|
||||
**Integration Quality Scale** even though custom integrations are not formally graded.
|
||||
Current tasks and priorities live only in
|
||||
[GitHub Issues](https://github.com/Matysh/houseplan-card/issues) and the linked
|
||||
[Project v2](https://github.com/users/Matysh/projects/1); this file keeps
|
||||
historical engineering direction. The original market rationale is archived at
|
||||
`legacy/docs/PRODUCT-2026-07-05.md`.
|
||||
|
||||
Phases 0–6 of the original plan (server-side config, room markup editor, device
|
||||
management, virtual devices, publication) are **done** — see CHANGELOG v1.3.0–v1.11.2.
|
||||
|
||||
+5
-3
@@ -3,9 +3,11 @@
|
||||
*Fixed with the owner on 2026-07-22. This document is a guard rail: features are
|
||||
built, improved and accepted **only** if they serve a job listed here. When a new
|
||||
idea appears, first find its row in this file; if there is none — it belongs to
|
||||
HA core, to another card, or nowhere. Companion documents:
|
||||
PRODUCT-IMPROVEMENT-PLAN.ru.md (current product audit and order of work),
|
||||
ROADMAP.md (engineering/quality backlog), UX-MODES.md (interaction model).
|
||||
HA core, to another card, or nowhere. The current order of work lives only in
|
||||
[GitHub Issues](https://github.com/Matysh/houseplan-card/issues) and the linked
|
||||
[Project v2](https://github.com/users/Matysh/projects/1). Companion
|
||||
documents: ROADMAP.md (historical engineering direction), UX-MODES.md
|
||||
(interaction model).
|
||||
TOUCH-SUPPORT.md fixes the input-support contract: touch is a guaranteed View
|
||||
surface, while every editor is desktop-first and best effort on touch.
|
||||
The old market snapshot is archived at `legacy/docs/PRODUCT-2026-07-05.md`.*
|
||||
|
||||
+10
-12
@@ -9,7 +9,8 @@
|
||||
> `docs/CHANGELOG.md` (English) and `docs/CHANGELOG.ru.md` (Russian, since
|
||||
> v1.42.0 — the user base is largely Russian-speaking, see the Telegram chat)**, STATUS.md for state changes
|
||||
> (versions, publication, infrastructure), DEVELOPMENT.md for new gotchas,
|
||||
> ARCHITECTURE.md for design changes, ROADMAP.md when plans move.
|
||||
> ARCHITECTURE.md for design changes. Work scope and status live in GitHub
|
||||
> Issues + Project v2, not in a parallel backlog document.
|
||||
|
||||
**Promotion rule (2026-08-08):** every new feature or material behaviour
|
||||
change must pass through a published beta/RC before stable. Stable release
|
||||
@@ -23,7 +24,7 @@ metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
|
||||
| Version | **v1.60.3-beta.2** everywhere (manifest, const.py, package.json, CARD_VERSION) — editor-context and touch-safety pre-release after v1.60.3-beta.1; `main` remains on stable v1.60.2 while the beta is published from `dev` |
|
||||
| Current local cycle | v1.60.3-beta.2 adds the touch pinch/misclick guard and HP-UX-11 editor chrome: primary toolbars are stable with pinned Close, while selection actions, transient tool controls and palettes use one stage-owned context tray. The review follow-up covers reduced motion, honest stale/group tests, vacuum-fit gesture isolation, global palette/group dismissal, focus and accessibility details. HP-PERF-01 now has same-runner base-vs-candidate reports and a blocking CI gate; HP-QA-01 keeps the reviewed Linux baseline of 20 deterministic scenarios. |
|
||||
| Workflow | Owner's rule since 2026-08-07: ordinary fixes/features are made **locally, without tests and without commits**. A requested pre-release gets a production build plus the smallest targeted unit/smoke set covering the changed surfaces, one tested `dev` commit/tag and a GitHub Release with `prerelease=true`; `main` stays untouched. The complete local frontend/backend/smoke gate runs only before a stable release, after which `main` is fast-forwarded to the exact tested `dev` SHA and the GitHub Release uses `prerelease=false`. Release bodies are short and bilingual (Russian first): only significant user changes get individual bullets, while minor/code-only work is grouped as `Мелкие исправления и улучшения` / `Small fixes and improvements`; every body ends with separate links to the Russian and English changelogs. Nothing is copied to the home instance by hand |
|
||||
| GitHub | https://github.com/Matysh/houseplan-card — `main` carries stable releases; pre-release tags may point directly at `dev`. Work lands on `dev` and is merged into `main` for a stable release, so `dev` is normally equal to or ahead of `main`, never behind. Push via SSH key `ha_jb` (remote git@github.com:…); API releases via the fine-grained PAT in `~/.git-credentials` (Contents R/W, issued 2026-07-23) |
|
||||
| GitHub | https://github.com/Matysh/houseplan-card — [Issues](https://github.com/Matysh/houseplan-card/issues) are the canonical task records and the linked [Project v2](https://github.com/users/Matysh/projects/1) is the canonical priority/status view; both must stay current. `main` carries stable releases; pre-release tags may point directly at `dev`. Work lands on `dev` and is merged into `main` for a stable release, so `dev` is normally equal to or ahead of `main`, never behind. Push via SSH key `ha_jb` (remote git@github.com:…); API releases via the fine-grained PAT in `~/.git-credentials` (Contents R/W, issued 2026-07-23) |
|
||||
| CI | v1.60.3-beta.2 uses the owner-approved targeted pre-release gate locally. Exact-SHA Ubuntu Validate remains mandatory and runs the broader matrix; `release.yml` withholds assets until every matching run finishes green |
|
||||
| HACS | Custom repository works. **Inclusion PR: hacs/default#9004** — open, valid, labeled, mergeable clean, never drafted. Queue: 1212 open, 835 older than ours. Merge rate COLLAPSED: 75 in July but almost all in the first decade, 0 in the last week (checked 2026-07-29) — maintainers process in rare bursts; ETA unknowable, months at best. Nothing actionable on our side |
|
||||
| Home instance | ha.jbstudio.pro (SSH port **22222**, key `ha_jb`; HA config root is `/mnt/data/supervisor/homeassistant` — `/config` does NOT exist in this SSH environment), last direct copy was **v1.57.0**; from v1.58.0 on it updates itself through HACS by tag (no scp) |
|
||||
@@ -215,16 +216,13 @@ metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
|
||||
|
||||
## Open items / watchlist
|
||||
|
||||
0. **Current product audit (2026-08-06)** — [`PRODUCT-IMPROVEMENT-PLAN.ru.md`](PRODUCT-IMPROVEMENT-PLAN.ru.md)
|
||||
(UX, functional integrity, architecture debt and prioritized roadmap). The
|
||||
previous v1.58.0 market/quality pack is preserved under
|
||||
[`legacy/docs/audit-v1.58.0`](../legacy/docs/audit-v1.58.0/AUDIT.md). Key
|
||||
takeaway: engineering depth is strong; **easy-floorplan ≈430★** is now the
|
||||
traction threat; P0s are demo GIF + forum posts + write-policy UI↔API align.
|
||||
Agents: read the pack before proposing features or refactors.
|
||||
**Partial follow-up from the archived audit landed on `dev` (2026-08-05):** P0-3 README diff, P0-4
|
||||
write-policy (`can_write` + admin_only default on), P3-4/P3-5 validation &
|
||||
hygiene. Still open: P0-1 demo GIF/video, P0-2 forum/Reddit posts.
|
||||
0. **Canonical backlog** — [GitHub Issues](https://github.com/Matysh/houseplan-card/issues)
|
||||
contain task scope and acceptance criteria; the linked
|
||||
[Project v2](https://github.com/users/Matysh/projects/1) contains the
|
||||
current priority and workflow status. Every open in-scope issue belongs in
|
||||
the Project. The former local product plan is preserved only as a snapshot at
|
||||
[`legacy/docs/PRODUCT-IMPROVEMENT-PLAN.ru.md`](../legacy/docs/PRODUCT-IMPROVEMENT-PLAN.ru.md)
|
||||
and must not be updated or used as a backlog.
|
||||
1. **hacs/default PR #9004** — accepted by the bot into the review queue ('New default
|
||||
repository' label). Minor issues ⇒ the bot drafts the PR (fix and re-ready).
|
||||
2. GitHub auth: fine-grained PAT (Contents R/W, issued 2026-07-23) in the sandbox
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Open spans + wall-centric Delete — design
|
||||
|
||||
> Superseded on 2026-08-06 by UX-02 in
|
||||
> `docs/PRODUCT-IMPROVEMENT-PLAN.ru.md`: opening a boundary, closing a boundary,
|
||||
> merging rooms, and deleting a room are now separate tools. This file remains
|
||||
> only as the historical rationale for the original open-span implementation.
|
||||
> Superseded on 2026-08-06 by the accepted UX-02 decision: opening a boundary,
|
||||
> closing a boundary, merging rooms, and deleting a room are separate tools.
|
||||
> This file remains only as the historical rationale for the original open-span
|
||||
> implementation; current work is tracked in GitHub Issues + Project v2.
|
||||
|
||||
Status: **approved in chat** (2026-08-05). Implementation plan follows after
|
||||
spec review.
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@
|
||||
|---|---|
|
||||
| `docs/audit-v1.58.0/` | Снимок аудита v1.58.0; заменён аудитом и планом улучшений v1.59.0-rc.1 |
|
||||
| `docs/PRODUCT-2026-07-05.md` | Старая продуктовая оценка с устаревшей таблицей рынка |
|
||||
| `docs/PRODUCT-IMPROVEMENT-PLAN.ru.md` | Последний снимок локального backlog; все актуальные пункты перенесены в GitHub Issues + Project v2 9 августа 2026 года |
|
||||
| `docs/SUN-CONTRAST.md` | Отклонённая модель солнечного контраста; реализована только описанная в актуальном `docs/SUN.md` кромка |
|
||||
| `docs/implementation-plans/` | Завершённые пошаговые планы реализации; действующие спецификации остаются в `docs/superpowers/specs/` |
|
||||
| `demo/dbg_click.mjs` | Одноразовая диагностика старой проблемы клика |
|
||||
@@ -16,6 +17,6 @@
|
||||
Актуальные точки входа:
|
||||
|
||||
- [пользовательская документация](../docs/README.ru.md);
|
||||
- [продуктовый аудит и план улучшений](../docs/PRODUCT-IMPROVEMENT-PLAN.ru.md);
|
||||
- [GitHub Issues](https://github.com/Matysh/houseplan-card/issues) и [Project v2](https://github.com/users/Matysh/projects/1) — единственный актуальный backlog;
|
||||
- [статус проекта](../docs/STATUS.md);
|
||||
- [архитектура](../docs/ARCHITECTURE.md).
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# House Plan — актуальный план улучшений продукта
|
||||
# House Plan — архивный снимок плана улучшений продукта
|
||||
|
||||
> **Архивировано 9 августа 2026 года.** Все актуальные пункты этого снимка
|
||||
> перенесены в [GitHub Issues](https://github.com/Matysh/houseplan-card/issues)
|
||||
> и Project v2. Этот файл больше не является backlog, не обновляется и хранится
|
||||
> только для истории решений.
|
||||
|
||||
Срез: **v1.60.3-beta.2**, 9 августа 2026 года.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Project audit — index
|
||||
|
||||
> **Archived:** this is the v1.58.0 snapshot. The current product audit is
|
||||
> [`docs/PRODUCT-IMPROVEMENT-PLAN.ru.md`](../../../docs/PRODUCT-IMPROVEMENT-PLAN.ru.md).
|
||||
> **Archived:** this is the v1.58.0 snapshot. Current work is tracked in
|
||||
> [GitHub Issues](https://github.com/Matysh/houseplan-card/issues) and Project v2;
|
||||
> the last local product-plan snapshot is archived at
|
||||
> [`legacy/docs/PRODUCT-IMPROVEMENT-PLAN.ru.md`](../PRODUCT-IMPROVEMENT-PLAN.ru.md).
|
||||
>
|
||||
> **Audience:** future humans and agents. Read this before proposing features,
|
||||
> refactors, or go-to-market work. Snapshot date: **2026-08-05**. Product
|
||||
|
||||
@@ -3720,15 +3720,10 @@ class HouseplanCard extends LitElement {
|
||||
|
||||
private _stagePointerDown(ev: PointerEvent): void {
|
||||
if (this._vacFit) return; // no pan/swipe while fitting the robot map
|
||||
// Furniture deliberately supports stay-open-on-canvas while a symbol is
|
||||
// armed. With no symbol selected, the same outside press is only dismiss:
|
||||
// consume it so it cannot also select or begin another canvas operation.
|
||||
if (this._mode === 'decor' && this._decorTool === 'furniture' && !this._furnPalette) {
|
||||
ev.preventDefault();
|
||||
this._suppressClick = true;
|
||||
this._decorTool = 'select';
|
||||
return;
|
||||
}
|
||||
// The shared secondary controller owns palette dismissal in window
|
||||
// capture, including the matching synthetic click. Do not duplicate that
|
||||
// lifecycle here: the former fallback left `_suppressClick` stuck when a
|
||||
// pointerup was lost or a pointercancel followed the dismiss press.
|
||||
if (this._kiosk) {
|
||||
this._cyclePausedUntil = Date.now() + 60000;
|
||||
if (this._pointers.size === 0) {
|
||||
|
||||
+15
-3
@@ -314,7 +314,7 @@ export const cardStyles = css`
|
||||
opacity: 0;
|
||||
}
|
||||
.bootveil .boothouse {
|
||||
position: static; /* .stage svg pins itself to inset:0 — not this one */
|
||||
position: static; /* the main .zoomwrap SVG is pinned to inset:0 — not this icon */
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
fill: var(--hp-accent);
|
||||
@@ -417,7 +417,12 @@ export const cardStyles = css`
|
||||
font-weight: 600;
|
||||
pointer-events: none;
|
||||
}
|
||||
.stage svg {
|
||||
/* Only the plan canvas owns the whole zoom wrapper. Context trays now live
|
||||
inside .stage as well, and may contain small SVG previews (furniture in
|
||||
particular); the old descendant-wide stage-SVG selector stretched every
|
||||
preview over the tray and stacked them into one blocking artefact. The
|
||||
vacuum trail/fit overlays have their own explicit geometry below. */
|
||||
.zoomwrap > svg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
@@ -1404,11 +1409,18 @@ export const cardStyles = css`
|
||||
}
|
||||
.modetab .closex:hover { opacity: 1; }
|
||||
.editbar .barclose {
|
||||
padding: var(--sp-2) var(--sp-3);
|
||||
box-sizing: border-box;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
.editbar .barclose ha-icon { flex: none; margin: 0; }
|
||||
.modetab.active {
|
||||
background: var(--hp-accent);
|
||||
color: var(--text-primary-color, #fff);
|
||||
|
||||
+35
-3
@@ -1867,16 +1867,48 @@ export interface OpeningTunnelGeometry {
|
||||
}
|
||||
|
||||
function tunnelFacePath(side: -1 | 1, pieces: OpeningWallPiece[]): string {
|
||||
const eps = 1e-9;
|
||||
const compact: OpeningWallPiece[] = [];
|
||||
for (const piece of [...pieces]
|
||||
.filter((candidate) => candidate.x1 > candidate.x0 && candidate.half > 0)
|
||||
.sort((a, b) => a.x0 - b.x0 || a.x1 - b.x1 || a.half - b.half)) {
|
||||
const tail = compact[compact.length - 1];
|
||||
// Atomic wall profiles may split one visually continuous wall at room or
|
||||
// model vertices. Painting every atom as its own SVG rectangle exposes
|
||||
// their shared edges as hairlines at fractional zoom. Equal-depth atoms
|
||||
// are one physical tunnel surface, so collapse them before rendering.
|
||||
if (tail
|
||||
&& piece.x0 <= tail.x1 + eps
|
||||
&& Math.abs(piece.half - tail.half) <= eps) {
|
||||
tail.x1 = Math.max(tail.x1, piece.x1);
|
||||
} else {
|
||||
compact.push({ ...piece });
|
||||
}
|
||||
}
|
||||
|
||||
const parts: string[] = [];
|
||||
for (const p of pieces) {
|
||||
if (!(p.x1 > p.x0) || !(p.half > 0)) continue;
|
||||
for (let i = 0; i < compact.length; i++) {
|
||||
const p = compact[i];
|
||||
const y = side * p.half;
|
||||
// Both faces overlap symmetrically across y=0 by a tiny amount. They are
|
||||
// subpaths of one SVG fill, so this closes fractional-zoom hairlines
|
||||
// without adding opacity or escaping the physical outer faces.
|
||||
const seam = Math.min(p.half * 0.02, 0.05);
|
||||
const y0 = -side * seam;
|
||||
parts.push(`M ${p.x0} ${y0} L ${p.x1} ${y0} L ${p.x1} ${y} L ${p.x0} ${y} Z`);
|
||||
let x0 = p.x0;
|
||||
let x1 = p.x1;
|
||||
const previous = compact[i - 1];
|
||||
const next = compact[i + 1];
|
||||
// A real thickness step cannot be collapsed into one rectangle. Overlap
|
||||
// only its touching internal edge; all strips remain subpaths of one fill,
|
||||
// so the overlap cannot stack alpha and never extends a tunnel endpoint.
|
||||
if (previous && Math.abs(previous.x1 - p.x0) <= eps) {
|
||||
x0 -= Math.min(Math.min(previous.half, p.half) * 0.02, 0.05);
|
||||
}
|
||||
if (next && Math.abs(p.x1 - next.x0) <= eps) {
|
||||
x1 += Math.min(Math.min(next.half, p.half) * 0.02, 0.05);
|
||||
}
|
||||
parts.push(`M ${x0} ${y0} L ${x1} ${y0} L ${x1} ${y} L ${x0} ${y} Z`);
|
||||
}
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ test('golden matrix covers required geometry, rendering and adaptive surfaces',
|
||||
const ids = GOLDEN_SCENARIOS.map((scenario) => scenario.id).join(' ');
|
||||
for (const token of ['geometry', 'diagonal-45-opening', 'openings', 'openings-hidden',
|
||||
'fill-light', 'fill-temp', 'fill-lqi', 'lighting', 'hover', 'zoom-040', 'zoom-250',
|
||||
'warm-remount', 'dialog-mobile', 'color-popover'])
|
||||
'warm-remount', 'dialog-mobile', 'color-popover', 'tray-wide', 'tray-medium',
|
||||
'tray-narrow'])
|
||||
assert.equal(ids.includes(token), true, token);
|
||||
assert.equal(new Set(GOLDEN_SCENARIOS.map((scenario) => scenario.mode)).has('plan'), true);
|
||||
assert.equal(new Set(GOLDEN_SCENARIOS.map((scenario) => scenario.mode)).has('devices'), true);
|
||||
@@ -27,3 +28,21 @@ test('golden matrix covers required geometry, rendering and adaptive surfaces',
|
||||
assert.equal(new Set(GOLDEN_SCENARIOS.map((scenario) => scenario.theme)).has('light'), true);
|
||||
assert.equal(new Set(GOLDEN_SCENARIOS.map((scenario) => scenario.theme)).has('dark'), true);
|
||||
});
|
||||
|
||||
test('editor tray golden contract covers every adaptive width in English and Russian', () => {
|
||||
const tray = GOLDEN_SCENARIOS.filter((scenario) => scenario.editorTray);
|
||||
assert.equal(tray.length, 6);
|
||||
const widths = {
|
||||
wide: (scenario) => scenario.viewport.width >= 1000,
|
||||
medium: (scenario) => scenario.viewport.width >= 600 && scenario.viewport.width < 1000,
|
||||
narrow: (scenario) => scenario.viewport.width < 600,
|
||||
};
|
||||
for (const [name, predicate] of Object.entries(widths)) {
|
||||
const languages = new Set(tray.filter(predicate).map((scenario) => scenario.language));
|
||||
assert.deepEqual(languages, new Set(['en', 'ru']), name);
|
||||
}
|
||||
assert.deepEqual(new Set(tray.map((scenario) => scenario.editorTray)), new Set([
|
||||
'plan-selection', 'plan-tool', 'group', 'decor-selection',
|
||||
'furniture-palette', 'decor-tool',
|
||||
]));
|
||||
});
|
||||
|
||||
@@ -417,9 +417,26 @@ test('openingTunnelGeometry: mixed atomic thickness clips each piece to its real
|
||||
assert.ok(g);
|
||||
assert.match(g.faces[0].d, /-1(?:\.0+)?\b/, '10 cm half-depth is present');
|
||||
assert.match(g.faces[0].d, /-2(?:\.0+)?\b/, '20 cm half-depth is present');
|
||||
assert.match(g.faces[0].d, /M -0\.02\b/,
|
||||
'touching thickness steps overlap instead of exposing an antialiasing seam');
|
||||
closeTo(g.maxY, 2);
|
||||
});
|
||||
|
||||
test('openingTunnelGeometry: equal atomic strips collapse into one path without hairlines', () => {
|
||||
const rooms = [{ id: 'r', poly: [[0, 0], [10, 0], [10, 6], [0, 6]] }];
|
||||
const walls = [[0, 3], [3, 5], [5, 7], [7, 10]].map(([x0, x1]) => ({
|
||||
key: wallKey([x0, 0], [x1, 0], pitch), a: [x0, 0], b: [x1, 0], cm: 15,
|
||||
}));
|
||||
const g = openingTunnelGeometry(
|
||||
rooms, { x: 5, y: 0, angle: 0, length: 6 }, walls, [], pitch, 5, 1,
|
||||
);
|
||||
assert.ok(g);
|
||||
for (const face of g.faces) {
|
||||
assert.equal((face.d.match(/\bM /g) || []).length, 1,
|
||||
'one continuous wall face must not expose the three internal SVG strip edges');
|
||||
}
|
||||
});
|
||||
|
||||
test('openingTunnelGeometry: virtual, zero-thickness, orphan and draft-only walls do not paint', () => {
|
||||
const rooms = [{ id: 'r', poly: [[0, 0], [10, 0], [10, 6], [0, 6]] }];
|
||||
const opening = { x: 5, y: 0, angle: 0, length: 2 };
|
||||
|
||||
Reference in New Issue
Block a user