chore(hygiene): убрать мёртвые скрипты и старый архив, разовые документы — в legacy/ (#678)

Волна 1 эпика #674 — мёртвое без риска, каждое имя проверено git grep по
текущему dev.

Удалено: шесть demo/shot_*.mjs без читателей (shot_furniture ещё и не
работает с #159), demo/capture_wall_strip_backup.mjs,
demo/benchmark_coordinate_write_barrier.mjs (нигде не запускался, но входил
в манифест smoke через demo/benchmark_*.mjs — теперь лист покрытия чист),
scripts/dev/styles-split.mjs (падает на текущем src/styles.ts),
docs/README.ru.md (индекс четырёх документов из 38 — роль у README.ru.md),
demo/README.md (одна строка в карте пакета AGENTS.md вместо него) и из
legacy/ — снимок аудита v1.58.0, черновик 089, завершённые планы,
продуктовые снимки и две разовые диагностики; история git хранит.
legacy/docs/SUN-CONTRAST.md остаётся: на него ссылаются src/sun.ts и SUN.md.

Перенесено в legacy/docs/: superpowers/specs (11 дизайн-документов до
процесса), QUALITY-560.md, ROADMAP.md, STATUS-FEATURES.md. Ссылки
поправлены там, где они были: FILTERING.md, ТЗ 006/007/058, testing-notes,
комментарии src/open-spans.ts и validation.py (только путь документа),
SCOPE.md, STATUS.md, smoke_household_journeys.mjs, опись legacy/README.md;
ложный маркер benchmark_coordinate_write_barrier снят с чек-листа.

Issue: #678
User-Visible: no
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
Claude
2026-09-27 16:23:39 +00:00
committed by claude[bot]
parent 16cf433164
commit c081f59a15
49 changed files with 37 additions and 2245 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ House Plan is one HACS package with two parts plus a demo harness:
- **Lovelace card** (`src/`, TypeScript + Lit) — the primary product, bundled to
the entry, manifest and hashed chunks under `dist/`.
- **Storage integration** (`custom_components/houseplan/`, Python) — the Home Assistant backend.
- **Demo harness** (`demo/`) — a self-contained Playwright page (`demo/srv/demo.html`) that renders the card against a fake `hass`, used for screenshots and the `smoke_*.mjs` end-to-end suite.
- **Demo harness** (`demo/`) — a self-contained Playwright page (`demo/srv/demo.html`) that renders the card against a fake `hass`, used for screenshots and the `smoke_*.mjs` end-to-end suite. The synthetic home is fully fictional (no real home data in public materials); the launcher is `demo/serve.mjs`, the stand copy of the bundle comes from `npm run bundle:sync`, golden scenes live in `demo/golden/` (its README), performance smokes in `demo/performance/`, the guard suite in `demo/guard/` and the live stand seed in `demo/stand/`.
## Read this first
+1 -1
View File
@@ -1208,7 +1208,7 @@ _LEGACY_MAX_ROOM_DRAFTS = 200
_LEGACY_MAX_DRAFT_SEGMENTS = 2000
MAX_PARTITIONS = 2000
MAX_WALL_COLUMNS = 500
# Open (virtual) wall stretches, docs/superpowers/specs/2026-08-05-open-spans-delete-design.md.
# Open (virtual) wall stretches, legacy/docs/superpowers/specs/2026-08-05-open-spans-delete-design.md.
# Every span is a piece of a shared boundary, so there can never be more of
# them than there are wall segments — the cap is the walls' one (AUD-159B6-03).
MAX_OPEN_SPANS = 500
-17
View File
@@ -1,17 +0,0 @@
# Synthetic demo home
A fully fictional house (plans, devices, states) used for README screenshots,
the demo GIF and headless smoke tests — so no real home data ever appears in
public materials.
- `srv/demo.html` — self-contained host page: `<ha-icon>`/`<ha-card>` stubs and a
fake `hass` (registries, states, `callWS`, `callService`, floors).
- `srv/assets/` — generated plan SVGs and `icons.js` (`node demo/gen_icons.mjs`,
needs the repo's devDependencies). The card bundle is copied from `dist/`:
`npm run bundle:sync` (копия стенда не коммитится, #255).
- `serve.mjs` — playwright launcher (route interception, no web server).
- `smoke_*.mjs` — feature smoke tests; run with a Chromium installed via
`PLAYWRIGHT_BROWSERS_PATH=<dir> npx playwright install chromium-headless-shell`.
Note for sandboxed sessions: `/tmp` does not survive; this directory is the
persistent home of the harness (docs/DEVELOPMENT.md has the LD_LIBRARY_PATH recipe).
@@ -1,56 +0,0 @@
#!/usr/bin/env node
// #291: same-process p95 of the complete config+layout boundary versus the
// pre-existing full-candidate clone contract. Batching makes the strict 20%
// ratio meaningful even on coarse/loaded CI timers.
import { performance } from 'node:perf_hooks';
import { makeLargeHouseFixture } from './fixtures/large-house.mjs';
import {
canonicalizeConfigGeometry, canonicalizeLayoutGeometry,
} from '../test-build/coordinate-canonicalization.js';
const WARMUPS = 30;
const SAMPLES = 120;
const BATCH = 10;
const MAX_RATIO = 1.2;
const fixture = makeLargeHouseFixture();
const baseline = () => {
JSON.parse(JSON.stringify(fixture.config));
JSON.parse(JSON.stringify(fixture.layout || {}));
};
const candidate = () => {
canonicalizeConfigGeometry(fixture.config);
canonicalizeLayoutGeometry(fixture.layout || {});
};
const measure = (operation) => {
const started = performance.now();
for (let index = 0; index < BATCH; index++) operation();
return (performance.now() - started) / BATCH;
};
for (let index = 0; index < WARMUPS; index++) {
baseline();
candidate();
}
const baselineSamples = [];
const candidateSamples = [];
for (let index = 0; index < SAMPLES; index++) {
if (index % 2) {
candidateSamples.push(measure(candidate));
baselineSamples.push(measure(baseline));
} else {
baselineSamples.push(measure(baseline));
candidateSamples.push(measure(candidate));
}
}
const p95 = (values) => [...values].sort((a, b) => a - b)[Math.ceil(values.length * 0.95) - 1];
const baselineP95 = p95(baselineSamples);
const candidateP95 = p95(candidateSamples);
const ratio = candidateP95 / baselineP95;
const report = {
fixture: 'large-house-v1', samples: SAMPLES, batch: BATCH,
baselineP95Ms: baselineP95, candidateP95Ms: candidateP95,
ratio, limit: MAX_RATIO, pass: ratio <= MAX_RATIO,
};
console.log(JSON.stringify(report, null, 2));
if (!report.pass) process.exitCode = 1;
-85
View File
@@ -1,85 +0,0 @@
/** Local-only visual proof for #275; external backup contents are never printed. */
import { mkdirSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { optimizePlans } from '../test-build/plan-optimizer.js';
import { launch } from './serve.mjs';
const input = process.argv[2];
const outputDir = resolve(process.argv[3] || '.');
const mode = process.argv.includes('--optimized') ? 'optimized' : 'raw';
const nodesArg = process.argv.find((value) => value.startsWith('--nodes='))?.slice(8) || '';
const nodes = nodesArg.split(';').filter(Boolean).map((pair) => pair.split(',').map(Number));
if (!input) {
console.error('usage: node demo/capture_wall_strip_backup.mjs <backup> <outdir> '
+ '[--optimized] [--nodes=x,y;x,y]');
process.exit(2);
}
const backup = JSON.parse(readFileSync(input, 'utf8'));
const payload = backup?.payload && typeof backup.payload === 'object' ? backup.payload : backup;
const source = {
config: payload?.config && typeof payload.config === 'object' ? payload.config : payload,
layout: payload?.layout && typeof payload.layout === 'object' ? payload.layout : {},
};
const rendered = mode === 'optimized'
? optimizePlans(source.config, source.layout)
: source;
const config = rendered.config;
const layout = rendered.layout;
if (!Array.isArray(config?.spaces) || !config.spaces.length) {
throw new Error('backup has no spaces');
}
mkdirSync(outputDir, { recursive: true });
const { page, browser } = await launch({ width: 1800, height: 1250 }, 1);
await page.emulateMedia({ reducedMotion: 'reduce' });
await page.evaluate(async ({ cfg, lay }) => {
const card = window.__card;
card._serverCfg = structuredClone(cfg);
card._layout = structuredClone(lay);
card._space = cfg.spaces[0].id;
card._setMode('plan');
card._tool = 'select';
card._cfgEpoch++;
card._modelCache = null;
card._frame = null;
card._wallUnionCache = null;
card._physicalBodiesCache = null;
card._lightBarrierCache = null;
card._isoGeometryCache.clear();
card.requestUpdate();
await card.updateComplete;
card._fitAll();
card.requestUpdate();
await card.updateComplete;
await new Promise((done) => requestAnimationFrame(() => requestAnimationFrame(done)));
}, { cfg: config, lay: layout });
const fullPath = resolve(outputDir, `${mode}-full.png`);
await page.screenshot({ path: fullPath, animations: 'disabled' });
for (let index = 0; index < nodes.length; index++) {
const center = await page.evaluate(([x, y]) => {
const card = window.__card;
const svg = (card.shadowRoot || card.renderRoot).querySelector('.stage svg');
const matrix = svg?.getScreenCTM?.();
if (!matrix) return null;
const point = new DOMPoint(x * 1000, y * 1000).matrixTransform(matrix);
return { x: point.x, y: point.y };
}, nodes[index]);
if (!center) continue;
const width = 420, height = 360;
const clip = {
x: Math.max(0, Math.min(1800 - width, center.x - width / 2)),
y: Math.max(0, Math.min(1250 - height, center.y - height / 2)),
width,
height,
};
await page.screenshot({
path: resolve(outputDir, `${mode}-node-${index + 1}.png`),
clip,
animations: 'disabled',
});
}
await browser.close();
console.log(JSON.stringify({ mode, full: fullPath, crops: nodes.length }));
-33
View File
@@ -1,33 +0,0 @@
// Capture: a curtain on the move — the semantic transition ring around a
// NEUTRAL plate (owner 2026-08-03). The pulse is frozen at a visible frame so
// the shot is deterministic.
import { launch } from './serve.mjs';
const { page, browser } = await launch({ width: 820, height: 760 }, 2);
await page.evaluate(async () => {
const c = window.__card;
c._serverCfg = { ...c._serverCfg, markers: [
{ id: 'm_gate', binding: 'device:d_gate', tap_action: 'cover', display: 'icon_ripple' },
{ id: 'm_mower', binding: 'device:d_mower', hidden: true },
] };
c._cfgEpoch++; c._regSignature = ''; c._maybeRebuildDevices();
c.hass = { ...c.hass, states: { ...c.hass.states,
'cover.gate': { entity_id: 'cover.gate', state: 'opening',
attributes: { friendly_name: 'Curtain', device_class: 'curtain' } } } };
c._setMode('view'); c._space = 'garden';
c.requestUpdate();
await c.updateComplete;
const st = document.createElement('style');
st.textContent = '.device-pulse.continuous.transition i:first-child{animation-delay:-1.1s!important;animation-play-state:paused!important;}';
(c.shadowRoot || c.renderRoot).appendChild(st);
});
await page.waitForTimeout(400);
const box = await page.evaluate(() => {
const c = window.__card;
const r = (c.shadowRoot || c.renderRoot).querySelector('.dev.activity-transition').getBoundingClientRect();
return { x: r.x, y: r.y, w: r.width, h: r.height };
});
const pad = 70;
await page.screenshot({ path: process.argv[2] || '/tmp/cover_move.png',
clip: { x: Math.max(0, box.x - pad), y: Math.max(0, box.y - pad), width: box.w + pad * 2, height: box.h + pad * 2 } });
await browser.close();
console.log('shot ok');
-70
View File
@@ -1,70 +0,0 @@
// Capture: one curtain in the four states an owner sees — closed, open,
// opening, closing (owner's contract 2026-08-04). The plate is the plain
// neutral badge in ALL of them; open/closed is told by the icon morph alone,
// and the two travelling ones add the breathing transition ring (frozen at a
// visible frame so the shot is deterministic).
import { launch } from './serve.mjs';
const { page, browser } = await launch({ width: 900, height: 520 }, 2);
const STATES = ['closed', 'open', 'opening', 'closing'];
await page.evaluate(async (STATES) => {
const c = window.__card;
const devices = {}; const entities = {}; const states = {};
STATES.forEach((s, i) => {
const id = 'd_cur' + i;
devices[id] = { id, name: 'Curtain ' + s, model: 'Roller shade driver E1',
area_id: 'garden', identifiers: [['demo', id]], entry_type: null, via_device_id: null };
entities['cover.cur' + i] = { entity_id: 'cover.cur' + i, device_id: id, platform: 'demo' };
states['cover.cur' + i] = { entity_id: 'cover.cur' + i, state: s,
attributes: { friendly_name: 'Curtain ' + s, device_class: 'curtain' } };
});
c.hass = { ...c.hass,
devices: { ...c.hass.devices, ...devices },
entities: { ...c.hass.entities, ...entities },
states: { ...c.hass.states, ...states } };
c._serverCfg = { ...c._serverCfg, markers: [
...STATES.map((s, i) => ({ id: 'm_cur' + i, binding: 'device:d_cur' + i,
tap_action: 'cover', display: 'icon_ripple' })),
{ id: 'm_mower', binding: 'device:d_mower', hidden: true },
{ id: 'm_gate', binding: 'device:d_gate', hidden: true },
] };
c._cfgEpoch++; c._regSignature = ''; c._maybeRebuildDevices();
const layout = { ...c._layout };
STATES.forEach((s, i) => {
const d = c._devices.find((x) => x.bindingRef === 'd_cur' + i);
if (d) layout[d.id] = { s: 'garden', x: 0.18 + i * 0.215, y: 0.5 };
});
c._layout = layout;
c._setMode('view'); c._space = 'garden';
c.requestUpdate();
await c.updateComplete;
const st = document.createElement('style');
st.textContent = '.device-pulse.continuous.transition i:first-child{animation-delay:-1.1s!important;animation-play-state:paused!important;}';
(c.shadowRoot || c.renderRoot).appendChild(st);
}, STATES);
await page.waitForTimeout(500);
// captions under each badge, so the four states are readable in the file
const box = await page.evaluate((STATES) => {
const c = window.__card;
const els = [...(c.shadowRoot || c.renderRoot).querySelectorAll('.devlayer .dev')]
.sort((a, b) => a.getBoundingClientRect().x - b.getBoundingClientRect().x);
let minX = 1e9, maxX = -1e9, minY = 1e9, maxY = -1e9;
els.forEach((el, i) => {
const r = el.getBoundingClientRect();
const cap = document.createElement('div');
cap.textContent = STATES[i];
cap.style.cssText = `position:fixed;left:${r.x + r.width / 2}px;top:${r.y + r.height + 10}px;`
+ 'transform:translateX(-50%);font:600 13px system-ui,sans-serif;color:#1c2530;'
+ 'letter-spacing:.02em;z-index:9999;padding:2px 7px;border-radius:6px;'
+ 'background:rgba(255,255,255,0.88);white-space:nowrap;';
document.body.appendChild(cap);
minX = Math.min(minX, r.x); maxX = Math.max(maxX, r.x + r.width);
minY = Math.min(minY, r.y); maxY = Math.max(maxY, r.y + r.height);
});
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
}, STATES);
const pad = 55;
await page.screenshot({ path: process.argv[2] || '/tmp/cover_states.png',
clip: { x: Math.max(0, box.x - pad), y: Math.max(0, box.y - pad),
width: box.w + pad * 2, height: box.h + pad * 2 + 22 } });
await browser.close();
console.log('shot ok');
-37
View File
@@ -1,37 +0,0 @@
// Four stills of the #146 day-cycle environment.
import { launch } from './serve.mjs';
const outDir = process.argv[2] || '/tmp';
const { page, browser } = await launch({ width: 700, height: 700 }, 1);
await page.emulateMedia({ reducedMotion: 'reduce' });
await page.evaluate(async () => {
const c = window.__card;
const sp = c._serverCfg.spaces.find((s) => s.id === 'f1');
sp.openings = [
{ id: 'wN', type: 'window', x: 0.30, y: 0.14, angle: 0, length: 0.08 },
{ id: 'wE', type: 'window', x: 0.96, y: 0.60, angle: 90, length: 0.08 },
{ id: 'wW', type: 'window', x: 0.04, y: 0.30, angle: 90, length: 0.08 },
];
sp.plan_url = ''; // hand-drawn plan: white paper — the risky case for a white day
sp.settings = { ...(sp.settings || {}), show_borders: true, fill_mode: 'glow' };
c._serverCfg.settings = { ...(c._serverCfg.settings || {}), north_deg: 0, sun_rays: true, bg_mode: 'daynight' };
c._cfgRev = (c._cfgRev || 0) + 1;
});
const shot = async (az, el, rising, name) => {
await page.evaluate(async ([az, el, rising]) => {
const c = window.__card;
c.hass = { ...c.hass, states: { ...c.hass.states, 'sun.sun': {
entity_id: 'sun.sun', state: el > 0 ? 'above_horizon' : 'below_horizon',
attributes: { azimuth: az, elevation: el, rising },
} } };
c.requestUpdate(); await c.updateComplete;
}, [az, el, rising]);
await page.waitForTimeout(300);
const stage = await page.evaluateHandle(() => window.__card.shadowRoot.querySelector('.stage'));
await stage.asElement().screenshot({ path: `${outDir}/${name}.png` });
};
await shot(95, -2, true, 'dn_dawn');
await shot(180, 60, false, 'dn_noon');
await shot(265, -2, false, 'dn_sunset');
await shot(0, -20, false, 'dn_night');
await browser.close();
console.log('shots done');
-63
View File
@@ -1,63 +0,0 @@
// Скриншоты библиотеки мебели: палитра и расставленная мебель на плане.
import { launch } from './serve.mjs';
const OUT = process.env.HP_SHOT_DIR || '/tmp';
const { page, browser } = await launch({ width: 900, height: 900 }, 2);
await page.evaluate(async () => {
const c = window.__card;
const sr = c.shadowRoot || c.renderRoot;
sr.querySelectorAll('.modetab')[2].click();
await c.updateComplete;
c._curSpaceCfg.decor = [];
c._decorTool = 'furniture';
c._cfgEpoch++; c.requestUpdate();
await c.updateComplete;
sr.querySelector('.furnitem[data-symbol="sofa"]').click();
await c.updateComplete;
});
await page.waitForTimeout(400);
await page.screenshot({ path: `${OUT}/furniture_palette.png` });
await page.evaluate(async () => {
const c = window.__card;
const P = 1000 / 240;
const cm = (v) => (v / 5) * P / 1000; // cm -> normalised
const put = (id, symbol, w, h, cx, cy, angle) => ({
id, kind: 'furniture', symbol,
x: cx / 1000 - cm(w) / 2, y: cy / 1000 - cm(h) / 2,
w: cm(w), h: cm(h), color: '#8d6e63', width: 3,
...(angle ? { angle } : {}),
});
// r1 living 40..550 x 140..580 | r2 kitchen 550..960 x 140..460
// r3 bedroom 550..960 x 460..860 | r4 hall 40..550 x 580..860
c._curSpaceCfg.decor = [
put('f1', 'sofa', 220, 90, 250, 140 + 37.5),
put('f2', 'coffee_table', 110, 60, 250, 300),
put('f3', 'tv', 120, 30, 250, 570),
put('f4', 'armchair', 90, 85, 90, 340, 90),
put('f5', 'table_dining', 140, 80, 700, 300),
put('f6', 'chair', 45, 45, 700, 240),
put('f7', 'chair', 45, 45, 700, 360, 180),
put('f8', 'fridge', 60, 65, 590, 140 + 27, 0),
put('f9', 'stove', 60, 60, 670, 140 + 25),
put('f10', 'kitchen_sink', 80, 60, 760, 140 + 25),
put('f11', 'dishwasher', 60, 60, 840, 140 + 25),
put('f12', 'bed_double', 160, 200, 700, 460 + 42),
put('f13', 'nightstand', 45, 40, 620, 470),
put('f14', 'nightstand', 45, 40, 780, 470),
put('f15', 'wardrobe', 100, 60, 910, 700, 90),
put('f16', 'washer', 60, 60, 120, 860 - 25, 180),
put('f17', 'toilet', 40, 70, 220, 860 - 29, 180),
put('f18', 'bathtub', 170, 75, 400, 860 - 31, 180),
put('f19', 'plant', 40, 40, 500, 620),
put('f20', 'shower', 90, 90, 490, 700),
];
c._decorTool = 'select';
c._decorSel = 'f1';
c._cfgEpoch++; c.requestUpdate();
await c.updateComplete;
});
await page.waitForTimeout(500);
await page.screenshot({ path: `${OUT}/furniture_plan.png` });
await browser.close();
console.log('shots written to', OUT);
-34
View File
@@ -1,34 +0,0 @@
import { launch } from './serve.mjs';
const { page, browser } = await launch({ width: 820, height: 760 }, 2);
await page.evaluate(async () => {
const c = window.__card;
c._serverCfg.markers = (c._serverCfg.markers || []).filter((m) => m.binding !== 'device:d_motion');
c._serverCfg.markers.push({ id: 'd_motion', binding: 'device:d_motion', display: 'icon_ripple' });
c._cfgEpoch++; c._regSignature = ''; c._maybeRebuildDevices();
// the flash is keyed to a WITNESSED off→on transition (owner's rule
// 2026-08-01: разовая вспышка в момент обнаружения) — cycle through off
const set = async (state) => {
c.hass = { ...c.hass, states: { ...c.hass.states,
'binary_sensor.hall_motion': { entity_id: 'binary_sensor.hall_motion', state,
attributes: { friendly_name: 'Motion', device_class: 'motion', linkquality: 64 } } } };
await c.updateComplete;
};
await set('off');
await set('on');
// freeze the pulse at a visible frame for a deterministic capture
const st = document.createElement('style');
st.textContent = '.device-pulse.short.event i{animation-delay:-0.4s!important;animation-play-state:paused!important;}';
(c.shadowRoot || c.renderRoot).appendChild(st);
});
await page.waitForTimeout(300);
const box = await page.evaluate(() => {
const c = window.__card;
const el = (c.shadowRoot || c.renderRoot).querySelector('.dev.activity-event');
const r = el.getBoundingClientRect();
return { x: r.x, y: r.y, w: r.width, h: r.height };
});
const pad = 70;
await page.screenshot({ path: process.argv[2] || '/tmp/motion_sense.png',
clip: { x: Math.max(0, box.x - pad), y: Math.max(0, box.y - pad), width: box.w + pad * 2, height: box.h + pad * 2 } });
await browser.close();
console.log('shot ok');
-27
View File
@@ -1,27 +0,0 @@
// Capture: the shoulder rulers + centre tick while PLACING a new opening
// (Opening tool, cursor hovering the wall centre) — owner 2026-08-03.
import { launch } from './serve.mjs';
const { page, browser } = await launch({ width: 900, height: 820 }, 2);
await page.evaluate(() => {
const c = window.__card;
const sp = c._serverCfg.spaces.find((s) => s.id === 'f1');
sp.openings = [];
c._setMode('plan'); c._activateOpeningPlacement('door');
c._cfgEpoch++; c.requestUpdate();
return c.updateComplete && true;
});
await page.waitForTimeout(300);
const pt = await page.evaluate(() => {
const c = window.__card;
const stage = c.renderRoot.querySelector('.stage');
const r = stage.getBoundingClientRect();
const [vx, vy, vw, vh] = stage.querySelector('svg').getAttribute('viewBox').split(' ').map(Number);
return { x: r.left + ((293.7 - vx) / vw) * r.width, y: r.top + ((141 - vy) / vh) * r.height,
sx: r.left, sy: r.top, sw: r.width, sh: r.height };
});
await page.mouse.move(pt.x, pt.y, { steps: 4 });
await page.waitForTimeout(300);
await page.screenshot({ path: process.argv[2] || '/tmp/opening_place.png',
clip: { x: pt.sx, y: pt.sy, width: pt.sw, height: Math.min(pt.sh, 420) } });
await browser.close();
console.log('shot ok');
+1 -1
View File
@@ -11,7 +11,7 @@
// телефона (390 px) — та самая, на которой ломается первый контакт.
//
// Граница честности: это синтетический стенд. Реальная установка владельца в
// этом заходе читалась только на чтение (профиль в docs/QUALITY-560.md),
// этом заходе читалась только на чтение (профиль в legacy/docs/QUALITY-560.md),
// добровольцев и физического Companion в проверке не было.
import { launch, check, finish, watchPage } from './serve.mjs';
+1 -1
View File
@@ -4,7 +4,7 @@ Agreed with the owner 2026-07-29. This document is the source of truth for the
mechanism; the code follows it.
HA registry deactivation is a separate runtime condition. Its full contract is
specified in `docs/superpowers/specs/2026-08-08-ha-disabled-devices-design.md`.
specified in `legacy/docs/superpowers/specs/2026-08-08-ha-disabled-devices-design.md`.
## Principle
-17
View File
@@ -1,17 +0,0 @@
# Документация House Plan
Этот каталог — точка входа в русскоязычную документацию House Plan.
> При расхождении этого каталога с `docs/USER-GUIDE.ru.md` и changelog верить им.
| Документ | Для кого | Что внутри |
|---|---|---|
| [Полное руководство пользователя](USER-GUIDE.ru.md) | Пользователи и администраторы Home Assistant | Установка, первая настройка, пространства, комнаты, стены, проёмы, устройства, визуальные состояния, заливки, подложка, солнце, пылесосы, киоск, обслуживание и диагностика |
| [Радары присутствия](RADAR.md) | Пользователи, администраторы и интеграторы | Поддерживаемые профили, привязка источников, установка и калибровка, статусы данных, приватность и диагностика |
| [Редактор подложки и декора](DECOR-EDITOR.md) | Пользователи, тестировщики и разработчики | Инструменты, единое выделение и трансформации, физические размеры, магнит, Undo/Redo, поведение картинки-подложки и совместимость старых полей |
| [Лестницы и переходы между этажами](STAIRS.md) | Пользователи, тестировщики и разработчики | Прямые и винтовые лестницы, размеры, направление подъёма, магнит, связь этажей, площадь и ограничения 2.5D |
| [GitHub Issues](https://github.com/Matysh/houseplan-card/issues) | Владелец, разработчики и контрибьюторы | Единственный актуальный backlog: задачи, приоритеты и решения; статус — в метках issue (`PROCESS.md` §9) |
Старые тематические документы в этом каталоге остаются инженерными спецификациями и историей решений. При расхождении пользовательского описания с интерфейсом текущей версии приоритет имеет новое руководство, а при расхождении с фактическим поведением — код текущей версии.
Неактуальные снимки аудитов, отклонённые проекты и одноразовые диагностические материалы собраны в [архиве legacy](../legacy/README.md).
+3 -3
View File
@@ -6,11 +6,11 @@ idea appears, first find its row in this file; if there is none — it belongs t
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 their
status labels (Project v2 was dropped on 2026-08-14, #139). Companion
documents: ROADMAP.md (historical engineering direction), UX-MODES.md
(interaction model).
documents: UX-MODES.md (interaction model); the historical engineering
direction is archived in `legacy/docs/ROADMAP.md`.
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`.*
The old market snapshot lives only in git history (removed by #678).*
## Mission
+6 -7
View File
@@ -57,9 +57,9 @@ same commit as the change it describes.
| Product scope | `docs/SCOPE.md` is the feature guard rail; `docs/TOUCH-SUPPORT.md` is the input-support contract — check both before accepting interaction work |
The feature surface since the 2026-07-17 snapshot and the early release
milestones moved to [`STATUS-FEATURES.md`](STATUS-FEATURES.md) (#634): they
are reference, not session entry. New feature-surface bullets go there, in the
same commit as the behaviour.
milestones are archived in [`legacy/docs/STATUS-FEATURES.md`](../legacy/docs/STATUS-FEATURES.md)
(#634, archived by #678): the feature surface is described by the changelog and
the user guide, not by a parallel list.
## Where things live
@@ -75,10 +75,9 @@ same commit as the behaviour.
0. **Canonical backlog** — [GitHub Issues](https://github.com/Matysh/houseplan-card/issues)
contain task scope and acceptance criteria; their **labels** carry priority
and workflow status (`PROCESS.md` §9). GitHub Projects is no longer used.
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.
and workflow status (`PROCESS.md` §9). GitHub Projects is no longer used;
the former local product plan was removed from the tree (#678, git history
keeps it) and must not be used as a backlog.
1. Privacy: legacy real-house plan sources (`assets/`) and screenshots were
removed from the current tree. Public documentation images are generated
from synthetic fixtures by the `Docs screenshots` workflow, accepted with `npm run docs:accept -- --reviewed`, and indexed in
+1 -1
View File
@@ -3,7 +3,7 @@
- Issue: https://github.com/Matysh/houseplan-card/issues/6
- Приоритет: P1
- Статус ТЗ: реализовано по HP-VAC-02 rev.7; целевой gate v1.61.0-beta.1 пройден, ожидается публикация
- Родительский контракт: `docs/superpowers/specs/2026-08-09-vacuum-integration-coverage-design.md`, §4.1
- Родительский контракт: `legacy/docs/superpowers/specs/2026-08-09-vacuum-integration-coverage-design.md`, §4.1
## Цель
@@ -3,7 +3,7 @@
- Issue: https://github.com/Matysh/houseplan-card/issues/7
- Приоритет: P1
- Статус ТЗ: реализовано по rev.7 с прямым owner override F6; целевой gate v1.61.0-beta.1 пройден, ожидается публикация
- Родительский контракт: `docs/superpowers/specs/2026-08-09-vacuum-integration-coverage-design.md`, §4.2
- Родительский контракт: `legacy/docs/superpowers/specs/2026-08-09-vacuum-integration-coverage-design.md`, §4.2
## Цель
+1 -1
View File
@@ -3,7 +3,7 @@
- Issue: https://github.com/Matysh/houseplan-card/issues/58
- Приоритет: P1
- Статус ТЗ: реализовано с owner override F6; целевой gate v1.61.0-beta.1 пройден, ожидается публикация
- Полная нормативная спецификация: `docs/superpowers/specs/2026-08-09-vacuum-integration-coverage-design.md`
- Полная нормативная спецификация: `legacy/docs/superpowers/specs/2026-08-09-vacuum-integration-coverage-design.md`
## Назначение документа
+1 -2
View File
@@ -618,8 +618,7 @@
their boundary clones have zero noise, every required truncation/
threshold/layout/frontend/backend/recursive mutant is killed, and the
large-house boundary p95 is no more than 20% over the same-run full-clone
baseline [auto: `model-invariants.test`, `mutation-gate`,
`benchmark_coordinate_write_barrier`].
baseline [auto: `model-invariants.test`, `mutation-gate`].
- [ ] Rejected save leaves the plan intact (v1.45.0, review R2-1): attach a new
background, make the config write fail (a second tab saving first is
enough) — the previously stored plan is still served, with the same or a
+2 -2
View File
@@ -129,7 +129,7 @@
## HA-disabled binding gate
The source-of-truth matrix is
`docs/superpowers/specs/2026-08-08-ha-disabled-devices-design.md` §17.
`legacy/docs/superpowers/specs/2026-08-08-ha-disabled-devices-design.md` §17.
`test/ha-binding-status.test.mjs` covers full/limited registry decisions and
the active-only state projection. The standalone demo exposes complete
`disabled_by` rows through both registry list WS commands plus
@@ -182,7 +182,7 @@ the active-only state projection. The standalone demo exposes complete
## Device display preview and face parity
The behaviour matrix is defined in
`docs/superpowers/specs/2026-08-08-device-display-preview-design.md` §22.
`legacy/docs/superpowers/specs/2026-08-08-device-display-preview-design.md` §22.
Pure source/value/presentation rules live in `test/device-presentation.test.mjs`.
`demo/smoke_device_preview_parity.mjs` compares the same live fixture across
the interactive plan, `hp-device-preview` and `houseplan-space-card`, including
+16 -10
View File
@@ -2,21 +2,27 @@
Здесь хранятся исторические материалы, которые больше не являются источником
истины для текущей версии House Plan. Они оставлены для расследования старых
решений и регрессий, но не участвуют в сборке, тестах или релизе.
решений и регрессий, но не участвуют в сборке, тестах или релизе. На них можно
ссылаться из кода и документов только как на историю решения — не как на
действующий контракт.
| Путь | Почему перенесено |
|---|---|
| `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/` — дизайн-документы до процесса (08-05…08-10), историческая справка: действующее ТЗ живёт в теле issue (#517), `docs/specs/` — архив |
| `demo/dbg_click.mjs` | Одноразовая диагностика старой проблемы клика |
| `demo/repro_issue3.mjs` | Репродуктор уже закрытой ошибки |
| `docs/SUN-CONTRAST.md` | Отклонённая модель солнечного контраста; реализована только описанная в актуальном `docs/SUN.md` кромка. На неё ссылаются `src/sun.ts` и `docs/SUN.md` как на историю решения |
| `docs/superpowers/specs/` | Одиннадцать дизайн-документов 2026-08-05…08-10, написанных до процесса. Историческая справка: действующее ТЗ живёт в теле issue (#517), `docs/specs/` — архив ТЗ. Код и ТЗ ссылаются на них по этому пути |
| `docs/ROADMAP.md` | Историческое направление инженерной работы с открытыми чекбоксами — параллельный бэклог, которого процесс не допускает (`PROCESS.md` §12); текущие задачи только в GitHub Issues |
| `docs/STATUS-FEATURES.md` | Параллельный список фич и вех (обрывается на v1.23.1); поверхность продукта описывают changelog и руководство пользователя |
| `docs/QUALITY-560.md` | Разовый отчёт о качестве по #560; на него ссылается комментарий `demo/smoke_household_journeys.mjs` |
Удалённое из архива (#678, в дереве больше нет — только история git):
снимок аудита `docs/audit-v1.58.0/`, черновик `docs/089-isometric-view-draft.md`,
завершённые планы `docs/implementation-plans/`, продуктовые снимки
`docs/PRODUCT-2026-07-05.md` и `docs/PRODUCT-IMPROVEMENT-PLAN.ru.md`, разовые
диагностики `demo/dbg_click.mjs` и `demo/repro_issue3.mjs`.
Актуальные точки входа:
- [пользовательская документация](../docs/README.ru.md);
- [GitHub Issues](https://github.com/Matysh/houseplan-card/issues) и [Project v2](https://github.com/users/Matysh/projects/1) — единственный актуальный backlog;
- [пользовательская документация](../README.ru.md) и [руководство](../docs/USER-GUIDE.ru.md);
- [GitHub Issues](https://github.com/Matysh/houseplan-card/issues) — единственный актуальный backlog, статус в метках (`PROCESS.md` §9);
- [статус проекта](../docs/STATUS.md);
- [архитектура](../docs/ARCHITECTURE.md).
-43
View File
@@ -1,43 +0,0 @@
import { launch } from './serve.mjs';
const { page, browser } = await launch();
const res = await page.evaluate(async () => {
const out = {};
const c = window.__card;
const sr = () => c.shadowRoot || c.renderRoot;
const calls = [];
c.hass = { ...c.hass, callService: (d, s, data) => { calls.push([d, s, data.entity_id]); return Promise.resolve(); } };
// glow на текущем пространстве
c._serverCfg = { ...c._serverCfg, spaces: c._serverCfg.spaces.map((s) => s.id !== c._space ? s : ({
...s, settings: { ...(s.settings || {}), fill_mode: 'glow' } })) };
c.requestUpdate(); await c.updateComplete;
out.mode = c._mode;
out.suppress = c._suppressClick;
out.holdFired = c._holdFired;
out.drag = !!c._drag;
// реальный клик по включённой лампе (элемент .dev)
const litDev = c._devices.find((d) => d.space === c._space && d.entities.some((e) => e.startsWith('light.') && c.hass.states[e]?.state === 'on'));
out.hasLit = !!litDev;
const el = [...sr().querySelectorAll('.dev')].find((e) => e.textContent.includes(litDev.name) || true);
// найдём элемент точно: по индексу устройства
const devEls = [...sr().querySelectorAll('.dev')];
out.devCount = devEls.length;
const target = devEls[c._devices.filter((d) => d.space === c._space).indexOf(litDev)] || devEls[0];
const before = calls.length;
c._infoCard = null;
// эмулируем полный цикл: pointerdown/up + click
const opts = { bubbles: true, composed: true };
target.dispatchEvent(new PointerEvent('pointerdown', { ...opts, pointerId: 5 }));
target.dispatchEvent(new PointerEvent('pointerup', { ...opts, pointerId: 5 }));
target.dispatchEvent(new MouseEvent('click', opts));
await c.updateComplete;
out.reaction = calls.length > before ? 'service:' + JSON.stringify(calls.at(-1)) : c._infoCard ? 'info' : 'NOTHING';
out.suppressAfter = c._suppressClick;
// и через прямой вызов
c._infoCard = null;
c._clickDevice(new MouseEvent('click'), litDev);
await c.updateComplete;
out.directReaction = calls.length > before + 1 ? 'service' : c._infoCard ? 'info' : 'NOTHING';
return out;
});
console.log(JSON.stringify(res, null, 1));
await browser.close();
-25
View File
@@ -1,25 +0,0 @@
// Репро №3: пользовательский путь — открыть диалог, выставить «комфорт от 25», сохранить
import { launch } from './serve.mjs';
const { page, browser } = await launch();
const res = await page.evaluate(async () => {
const out = {};
const c = window.__card;
const sr = () => c.shadowRoot || c.renderRoot;
// living room: единственный термометр 22.4 → подменим на 24.0 как у пользователя
const h = window.__mkHass();
h.states = { ...h.states, 'sensor.living_temp': { ...h.states['sensor.living_temp'], state: '24.0' } };
c.hass = h; c._regSignature=''; c._maybeRebuildDevices(); await c.updateComplete;
// путь пользователя: диалог → режим temp, min=25 (макс не трогаем) → сохранить
c._openSpaceDialog('edit', 'f1'); await c.updateComplete;
out.dialogInit = { min: c._spaceDialog.tempMin, max: c._spaceDialog.tempMax, fill: c._spaceDialog.fillMode };
c._spaceDialog = { ...c._spaceDialog, fillMode: 'temp', tempMin: 25 };
await c._saveSpaceDialog(); await c.updateComplete;
const sp = c._serverCfg.spaces.find(s=>s.id==='f1');
out.savedSettings = sp.settings;
const styles = [...sr().querySelectorAll('.room.styled')].map(r => (r.getAttribute('style')||'').match(/--room-fill:([^;]+)/)?.[1]);
out.livingFill = styles[0];
out.expected = '#4fc3f7 (blue: 24 < 25)';
return out;
});
console.log(JSON.stringify(res, null, 1));
await browser.close();
-301
View File
@@ -1,301 +0,0 @@
# #89 — Опциональный объёмный 2.5D/изометрический вид плана
**Статус:** исследование завершено; черновик продуктового и технического решения
**Дата:** 2026-08-11
**Приоритет:** P1 — высокая продуктовая ценность, высокая стоимость реализации
**Область:** режим просмотра и киоск; редакторы в первую версию не входят
**Исходные референсы:** предоставленные владельцем проекта `isometric-plan-demo.zip` и изображение `photo_2026-08-11_18-30-38.jpg`
## 1. Краткое решение
Добавить в Houseplan опциональный **«Объёмный вид»** — стилизованное 2.5D-представление существующего плана с приподнятыми стенами, видимыми боковыми гранями, мягкой тенью и изометрической камерой.
Это не отдельный редактор и не новая модель плана. Комнаты, стены, проёмы, перегородки, колонны, устройства, Glow и все состояния продолжают использовать текущие данные. Меняется только способ их проекции и визуализации.
Рекомендуемая первая пользовательская версия:
- переключаемый плоский/объёмный вид;
- только режим просмотра и киоск;
- фиксированный проверенный ракурс без свободного вращения камеры;
- редакторы всегда открываются в обычном плоском виде;
- без WebGL/Three.js и без 32 копий всего SVG;
- стены строятся из канонической геометрии Houseplan, проёмы действительно вырезаются из объёма;
- Glow, солнечные лучи, заливки и hover остаются функционально теми же эффектами на плоскости пола;
- диалоги и tooltips остаются обычным экранным UI и не наклоняются вместе с планом.
## 2. Пользовательская ценность
Ценность оценивается как **очень высокая**:
- план превращается из утилитарной схемы в визуально сильный центр dashboard;
- различия стен, комнат, проёмов и внешних зон считываются быстрее;
- скриншоты и демонстрации продукта становятся существенно убедительнее;
- Houseplan получает заметное визуальное отличие от обычных floorplan-карточек;
- существующая модель данных уже содержит большую часть необходимой геометрии — пользователь не строит второй план.
При этом объёмный вид не должен становиться обязательным. Плоский вид точнее для редактирования, плотных планов и слабых устройств и остаётся полностью поддерживаемым.
## 3. Что показало исследование демо
Переданный demo — удачный визуальный proof of concept, но не готовая архитектура для Houseplan.
Он использует:
- один SVG с вручную заданной геометрией;
- CSS `perspective`, `rotateX`, `rotateZ` и `preserve-3d`;
- 32 копии одного контура стен, поднятые последовательными `translateZ`, чтобы имитировать толщину по высоте;
- отдельный верхний контур стен;
- статические подписи и устройства внутри наклонённой плоскости;
- drag для вращения и wheel для масштаба.
Для маленькой статической сцены это работает и хорошо показывает продуктовый эффект. Прямой перенос подхода в Houseplan не подходит, потому что:
1. Геометрия Houseplan динамическая: смешанная толщина, T/X-стыки, перегородки, круглые и квадратные колонны, двери, окна и ворота.
2. В карточке есть тяжёлые динамические слои: Glow, spill через проёмы, солнечные лучи, hover, vacuum overlays и HTML-маркеры.
3. 20–32 копии сложных wall paths заметно увеличат DOM, raster/compositing cost и вероятность швов.
4. CSS 3D-контекст разрушается или уплощается рядом свойств, уже используемых карточкой: `filter`, opacity на предках, `clip-path`, mask и часть blend/compositing-сценариев.
5. Сейчас day/night использует `filter: brightness(...)` на `.zoomwrap`, а Glow — `mix-blend-mode: screen`; слепое помещение существующего дерева в CSS 3D создаёт высокий риск регрессий.
6. Координаты устройств и room labels сейчас считаются для плоского `viewBox` и выводятся HTML-слоем. Их нужно проецировать тем же каноническим преобразованием, иначе они разойдутся с планом.
Вывод: demo подтверждает **реализуемость и ценность визуального направления**, но production-рендерер нужно строить на геометрии Houseplan.
## 4. Рекомендуемая архитектура
### 4.1. Не полноценный 3D, а детерминированная 2.5D-проекция
Для первой версии рекомендуется обычный SVG с явной 2D-проекцией, а не CSS-стопка и не WebGL:
- floor/decor/room/glow-слои помещаются в SVG-группу с одной аффинной изометрической матрицей;
- верх стены — каноническое объединённое тело стены, спроецированное и сдвинутое на визуальную высоту;
- боковые грани — четырёхугольники, построенные из граничных рёбер wall body и вектора высоты;
- видимые боковые грани фильтруются по направлению камеры и сортируются по глубине;
- HTML-маркеры получают экранные координаты через ту же чистую функцию проекции;
- tooltips, dialogs и системные controls остаются вне проецируемой сцены.
Это позволяет сохранить обычный SVG compositor, mask/blend-поведение Glow и предсказуемую деградацию, а число элементов растёт по числу реальных граней, а не умножается на 20–32 слоя.
### 4.2. Канонические источники геометрии
Нельзя создавать вторую независимую модель стен. Рендерер обязан использовать:
- `wallBodiesGeometry()` / объединённые wall bodies из `src/wall-thickness.ts`;
- текущие opening cuts/tunnel geometry;
- существующую модель перегородок и колонн;
- текущие room polygons и decor geometry;
- текущий канонический light resolver и описанную в `docs/LIGHT.md` семантику.
Возможные новые модули:
- `src/render/isometric-projection.ts` — чистая математика проекции и обратного hit mapping;
- `src/render/isometric-walls.ts` — top/side faces и depth ordering;
- `src/render/isometric-scene.ts` — композиция слоёв;
- детерминированные fixtures/golden matrix отдельно от основной карточки.
Кэш геометрии строится по содержательному fingerprint геометрии, а не только по `_cfgEpoch`.
### 4.3. Высота стен
В модели сейчас нет полноценной высоты помещений/стен. В первой версии высота — **визуальный параметр представления**, а не архитектурный размер:
- единое безопасное значение по умолчанию;
- один общий параметр для карточки/пространства, если настройка вообще выводится пользователю;
- одинаковая высота у обычных стен, перегородок и колонн;
- виртуальные стены остаются линиями пола и не получают объём.
Не следует выводить высоту в сантиметрах: это создаст ложное обещание настоящей 3D-модели.
## 5. UX первой версии
### 5.1. Переключение
- В режиме просмотра появляется компактная кнопка с `mdi:cube-outline` и accessible name «Объёмный вид».
- Повторное нажатие возвращает «Плоский вид».
- Переключение не изменяет геометрию и не создаёт запись в command stack.
- Предпочтение вида хранится отдельно от модели плана; оно не должно создавать конфликтов совместного редактирования.
- В киоске используется настроенный вид по умолчанию; скрытая панель не должна делать возврат в плоский вид невозможным из настроек.
- Вход в любой редактор временно показывает плоский вид. После выхода восстанавливается предыдущий режим просмотра.
Плоский вид остаётся значением по умолчанию для существующих установок и fallback при ошибке/неподдерживаемом окружении.
### 5.2. Камера и навигация
MVP использует один тщательно подобранный ракурс либо 2–3 пресета. Свободное вращение и изменение tilt не входят в первую версию.
Причины:
- свободное вращение конфликтует с pan, pinch zoom, long press и кликами устройств;
- оно требует динамической сортировки граней на каждом кадре;
- маркетинговый эффект достигается фиксированным хорошим ракурсом;
- фиксированный ракурс детерминирован для golden image и поддержки.
Обычный zoom/pan сохраняется. Изменение масштаба должно быть совместимо с #82 и не менять фокус плана скачком при переключении вида.
### 5.3. Устройства и подписи
Для первой версии рекомендуется:
- позиция marker/room label проецируется вместе с планом;
- сама интерактивная карточка устройства остаётся достаточно читаемой и получает мягкую объёмную тень;
- tooltip/dialog всегда экранные и не наклоняются;
- touch target не уменьшается из-за визуального наклона;
- tab order и accessible name совпадают с плоским режимом.
Полностью «лежащие на полу» подписи красивее, но хуже читаются. Допустим компромисс: лёгкое согласование с ракурсом для подложки и screen-facing содержимое. Точный вариант выбирается по прототипу и a11y-проверке.
## 6. Поведение слоёв
| Слой/объект | Поведение в объёмном виде |
|---|---|
| Пол/заливка комнаты | Лежит на нижней плоскости, текущая семантика цвета сохраняется |
| Room hover | Затемняет заливку и подсвечивает внутренние границы без вспышки Glow |
| Физические стены | Верхняя поверхность + видимые боковые грани |
| Перегородки | Как физические стены; комнату автоматически не делят |
| Квадратные/круглые колонны | Экструдируются из своей текущей геометрии |
| Виртуальные стены | Пунктир на плоскости пола, без высоты |
| Дверь | Проём реально разрывает стену; символ состояния сохраняется. Вертикальная створка — polish-этап |
| Окно | Разрыв объёма с отдельным стилизованным оконным элементом; без моделирования реальной высоты подоконника |
| Ворота | Разрыв объёма; две наружные створки сохраняются |
| Opening tunnel fill | Цвет комнаты на полу, без швов; семантика Glow не меняется |
| Glow и spill | На плоскости пола, стеновые барьеры и проёмы работают по `docs/LIGHT.md` |
| Солнечные лучи | На плоскости пола, геометрия и настройки остаются текущими |
| Decor/backdrop | Лежит на floor plane; не создаёт объём автоматически |
| Vacuum path/outline | На floor plane; robot marker остаётся интерактивным |
| Device markers | Проецированная позиция, безопасный z-order и неизменная логика состояний |
| Tooltips/dialogs | Экранный overlay поверх сцены |
| `show_borders: false` | Невидимые стены остаются невидимыми, но продолжают влиять на площадь и свет |
## 7. Обязательные edge cases
Реализация и fixtures должны покрыть:
1. Выпуклые и вогнутые комнаты, отверстия и несколько несвязанных контуров.
2. Смешанную толщину стен, короткие сегменты, T/X-стыки и сложные объединения.
3. Проём у угла, несколько соседних проёмов, дверь/окно/ворота шире толщины стены.
4. Перегородку, примыкающую к стене без визуального шва.
5. Квадратную повёрнутую и круглую колонну.
6. Виртуальную стену между двумя толстыми стенами.
7. Комнату без физических стен и старую конфигурацию без thickness.
8. Glow с несколькими источниками, spill через открытые проёмы и hover комнаты.
9. Day/night brightness, непрозрачные/полупрозрачные заливки и additive blending.
10. Большую подложку, decor, текст, пунктирные линии и мебель.
11. Скрытые/удалённые/недоступные устройства и все режимы device presentation.
12. Touch: pinch не вызывает click, long press не оставляет phantom pan.
13. Возврат на вкладку: сцена не мигает и не пересобирается из пустого состояния.
14. Очень широкий/высокий план, detached terrace/porch и несколько пространств.
15. Светлая/тёмная тема, kiosk, reduced motion и high zoom.
## 8. Производительность и технические ограничения
- Не клонировать весь SVG или wall body десятки раз.
- Число side faces должно быть O(числу граничных рёбер), а не O(рёбра × визуальная высота).
- Pan/zoom не пересчитывает модельную геометрию; меняется только transform/viewBox.
- Геометрия стен пересчитывается только при изменении содержательного fingerprint.
- Цель на детерминированном большом fixture: плавный pan/zoom на desktop и не более 20% регрессии frame time относительно плоского вида.
- Переключение вида не должно показывать пустой/чёрный промежуточный кадр.
- При нехватке возможностей или исключении renderer карточка безопасно возвращается в плоский вид и сообщает диагностируемую причину в dev log.
- Новая тяжёлая runtime-зависимость и WebGL не допускаются без отдельного архитектурного решения.
## 9. Доступность
- «Объёмный вид» — визуальная альтернатива, не отдельный набор функций.
- Все устройства, комнаты и действия доступны с клавиатуры так же, как в плоском виде.
- Focus ring виден и не обрезается стеной/контейнером.
- Screen reader получает те же имена и состояния.
- `prefers-reduced-motion` отключает переход между проекциями, но не сам режим.
- Минимальные touch targets сохраняются в экранных координатах.
- Плоский вид остаётся fallback для пользователей, которым перспектива мешает чтению.
## 10. План реализации
### Этап 0 — технический spike, 2–4 рабочих дня
- детерминированный fixture со стенами, mixed thickness, проёмами, колонной, Glow и устройствами;
- два прототипа: CSS wall slices и явные side faces;
- замеры Chrome/Edge, Firefox и Safari/WebKit;
- проверка Glow/mask/blend, day/night и HTML overlays;
- ADR с окончательным выбором renderer.
Spike не включается пользователям и не считается завершением issue.
### Этап 1 — ship-ready фиксированный объёмный вид, 8–15 рабочих дней
- чистая математика проекции;
- floor plane, wall top и side faces;
- проёмы, перегородки и колонны;
- проекция markers/labels и pointer mapping;
- переключение view/editor/kiosk;
- кэш, fallback, unit/integration/golden/performance gates;
- RU/EN документация и changelog.
### Этап 2 — визуальный polish уровня референса, ещё 8–15 рабочих дней
- улучшенные двери, окна и ворота;
- мягкие тени/ambient depth без дорогого dynamic lighting;
- аккуратные floor/platform edges;
- доводка markers/labels и occlusion;
- дополнительные пресеты камеры и качества.
### Отдельный будущий этап — свободная камера, ещё 5–10+ рабочих дней
Свободное вращение/tilt, gesture arbitration и динамический depth sort не входят в базовый scope. Их ценность ниже, а риск заметно выше.
## 11. Оценка сложности
| Вариант | Срок | Риск | Оценка |
|---|---:|---:|---|
| Демонстрационный CSS stack на одном fixture | 2–4 дня | Средний | Хорош для spike, не для релиза |
| Production MVP с фиксированным ракурсом | 8–15 дней | Высокий | Рекомендуемый первый релиз |
| Визуально отполированный вариант уровня референса | 16–30 дней суммарно | Высокий | Реалистичная конечная цель |
| Свободная камера поверх polished renderer | +5–10 дней | Высокий | Позже, только по подтверждённой ценности |
| Настоящий WebGL/Three.js 3D renderer | 1–2+ месяца | Очень высокий | Не рекомендуется для этой цели |
Общая оценка: **L/XL, высокий архитектурный и визуальный риск, но очень высокая продуктовая отдача**. Сам эффект дешёв в статичном demo; дорого стоит сохранение всей существующей интерактивности и световой модели без регрессий.
## 12. Риски и меры
| Риск | Вероятность/влияние | Мера |
|---|---|---|
| Glow/blend/filter ломает CSS 3D | Высокие | Обычная SVG 2.5D-проекция вместо вложенного `preserve-3d` |
| Неверные T/X-стыки и проёмы | Высокие | Только канонические union bodies + geometry fixtures |
| Маркеры расходятся с планом | Высокие | Одна pure projection для SVG и HTML overlays |
| Падение FPS на больших планах | Средние/высокие | Side faces вместо десятков slices, кэш и perf gate |
| Нечитаемые подписи | Средние | Screen-facing/гибридный marker prototype и a11y review |
| Touch misclick при навигации | Средние/высокие | В MVP нет свободной камеры; существующие pan/pinch правила сохраняются |
| Режим превращается в второй renderer со своей логикой | Высокие | Общие resolvers/geometry; новый слой отвечает только за projection/presentation |
| Референс обещает больше, чем модель умеет | Средние | Называть «Объёмный вид», не «полноценный 3D»; явно ограничить высоту и окна |
## 13. Не входит в первую версию
- редактирование геометрии в перспективе;
- пользовательская высота каждой стены/комнаты;
- 3D-мебель и импорт моделей;
- настоящая физика света и динамические тени;
- свободный orbit camera;
- WebGL/Three.js;
- автоматическое превращение растровой подложки в 3D;
- изменение формата существующих room/wall/opening данных.
## 14. Acceptance criteria пользовательской версии
1. Пользователь может переключить текущий план между плоским и объёмным видом без изменения данных.
2. Существующие планы открываются плоскими и не требуют миграции.
3. В объёмном виде физические стены, перегородки и колонны имеют непрерывные top/side faces; виртуальные стены не экструдируются.
4. Двери, окна и ворота образуют реальные разрывы объёма, без тонких швов и торцов внутри проёма.
5. Glow, spill, sunlight, room fill и hover сохраняют текущую семантику и не мигают.
6. Device markers, room controls, tooltips и dialogs остаются кликабельными, читаемыми и доступными с клавиатуры.
7. Открытие редактора показывает плоский вид; выход восстанавливает предыдущий просмотр.
8. Zoom/pan/fit работают без скачка, phantom click и изменения сохранённой геометрии.
9. При ошибке объёмного renderer доступен безопасный плоский fallback.
10. Пройдены unit tests проекции/side faces, smoke interactions, golden matrix и performance comparison на детерминированных fixtures.
11. Обновлены `README.md`, `docs/USER-GUIDE.ru.md`, RU/EN changelog и release screenshots.
## 15. Рекомендуемая поставка
Фичу лучше вести отдельным релизным циклом:
1. внутренняя beta/spike без публичного toggle;
2. beta с фиксированным объёмным видом и полным fallback;
3. beta с doors/windows/markers polish и performance fixes;
4. stable только после golden review на реальных больших планах и проверки мобильного просмотра.
Главный критерий успеха — не максимальная «трёхмерность», а визуально сильный вид без потери надёжности Houseplan как интерактивной HA-карточки.
-86
View File
@@ -1,86 +0,0 @@
# Product assessment & market position
*Written 2026-07-05. Sources: GitHub API star counts and HA docs verified 2026-07-06;
see links inline. Update this file when the landscape shifts.*
> **2026-08-05 refresh.** A full audit pack now lives under
> [`audit-v1.58.0/AUDIT.md`](audit-v1.58.0/AUDIT.md) (market / quality / functional / recommendations).
> **Critical market change:** [easy-floorplan](https://github.com/nicosandller/easy-floorplan)
> grew from **11★ → ≈430★** in ~one month and is a real GUI peer — the July claim
> that the niche is “currently unoccupied” is **obsolete**. Our technical moat
> (server-side integration, area-bound rooms, overlays, quality-scale) still
> holds; traction and messaging do not. Prefer [`AUDIT-MARKET.md`](audit-v1.58.0/AUDIT-MARKET.md)
> for current competitor numbers until this file is rewritten.
## What the product is
An interactive floor plan for Home Assistant delivered as one HACS package:
a storage **integration** (server-side config, WS API, file uploads, auth) + a
**Lovelace card** (rendering, room markup editor, drag layout, zoom, live states,
temperature, Zigbee LQI, device metadata with PDF manuals, virtual markers,
live robot vacuums, en/ru localization). GUI-first: no YAML, no hand-made SVG.
## Competitive landscape (stars verified 2026-07-06)
| Competitor | Stars | Approach | Weakness we exploit |
|---|---|---|---|
| [ha-floorplan](https://github.com/ExperienceLovelace/ha-floorplan) (incumbent) | 1 562 | hand-made SVG + YAML/CSS/JS rules | steep barrier: Inkscape + YAML; no GUI editor; config in card YAML |
| native `picture-elements` | built-in | absolute % coordinates in YAML | trial-and-error positioning, no zones, no editor |
| native Areas/Home dashboard (2025.4→2025.12) | built-in | auto card grid by area/floor | **not spatial** — leaves the visual-plan niche open |
| [Dwains Dashboard](https://github.com/dwainscheeren/dwains-lovelace-dashboard) | 2 049 | auto-generated menus | not a floor plan |
| [Bubble Card](https://github.com/Clooos/Bubble-Card) | 4 391 | GUI card collection | not a floor plan; sets the GUI-quality bar |
| [zigbee-floorplan-card](https://github.com/TheLarsinator/zigbee-floorplan-card) | 71 | LQI over a plan image | single-purpose; validates our LQI feature |
| [kishorviswanathan/ha-floorplan](https://github.com/kishorviswanathan/ha-floorplan) (2026-01) | 155 | external web editor → YAML export | editor outside HA, still YAML at runtime |
| [Padraigggs-ha-interactive-floorplan](https://github.com/Padraiggg/Padraigggs-ha-interactive-floorplan) (2026-03) | 41 | editor+viewer cards, "no YAML" | weeks old, card-only, no server-side integration |
| [easy-floorplan](https://github.com/nicosandller/easy-floorplan) (2026-05) | 11→**≈430 by 2026-08-05** | draw walls/furniture in the card | drawing-centric (still our non-goal) but **no longer immature** — fastest GUI peer; see AUDIT-MARKET |
**Demand evidence:** a dedicated [Floorplan forum category](https://community.home-assistant.io/c/third-party/floorplan/28);
the "100% Floorplan UI" mega-thread (500k+ views); a visible 2025–2026 wave of new
floorplan projects (three launched in the last six months alone). The pain is constant:
*people want a floor plan without Inkscape and YAML.*
## Honest assessment
**Usefulness — high.** A floor plan is the most natural "at a glance" home UI, and the
GUI-first workflow (upload image → outline rooms → devices appear → drag) removes the
exact barrier that keeps most users on lists of entity cards.
**Demand — real but niche-shaped.** Floorplan is a perennial top request, but the
audience is enthusiasts with wall tablets. Realistic trajectory given the field:
hundreds of stars in the first year *if* discoverability is solved (HACS default +
demo GIF + forum post). Bubble Card (4.4k★) proves polished GUI cards can go
quasi-mainstream; ha-floorplan's 1.5k★ with a hostile workflow shows the demand floor.
**Unique technical position — still real; market exclusivity — gone.** Nobody else
combines: server-side config (integration + `.storage`, survives dashboard edits,
shared across users/devices, optimistic locking, live multi-client sync) + in-card
room polygon editor bound to HA areas + curated auto-placement + drag layout +
LQI/temperature/glow/sun/vacuum overlays + config flow + en/ru localization +
CI/quality-scale discipline. The incumbent (ha-floorplan) is still YAML/SVG-locked.
But **easy-floorplan** (2026-08) is a shipping GUI card with an order of magnitude
more stars than us — card-only / furniture-drawing, yet owning the “no YAML” mindshare.
Our moat grows if we integrate deeper with the areas/floors registry (Phase 9) and
*tell that story* in the README/demo — that is the direction HA core itself is
signalling with the native Areas dashboard.
**Risks.**
1. *HA core ships a native spatial plan.* The Areas dashboard is grid-based today, but
core moving spatial would commoditize us. Mitigation: registry integration, speed.
2. *Discoverability.* 0 stars until the HACS queue (~2 months) clears. Mitigation:
demo assets + forum/Reddit showcase now, custom-repo installs meanwhile.
3. *Single maintainer bus factor* — mitigated by docs/ discipline and tests.
4. *Frontend API churn* (undocumented hass internals custom cards rely on).
Mitigation: minimal surface, CI against beta HA (add to Phase 8).
## Recommended next moves (priority order)
1. **Demo GIF + English forum/Reddit posts** — cheapest adoption lever, do before the
HACS queue clears so the storefront lands with social proof.
2. **Phase 7 quality items** (runtime_data, unloading, storage migrations, single_config_entry)
— cheap now, expensive later; also a credibility marker for reviewers.
3. **Registry-driven onboarding** (import floors/areas as spaces/rooms suggestions) —
the feature that makes first-run magical and that no competitor has.
4. **Editable icon rules** — removes the last dacha DNA and answers the #1 predictable
user complaint ("wrong icon for my device").
5. **Click actions (toggle from the plan)** — most requested behavior in every
floorplan thread; we currently only open info dialogs.
-456
View File
@@ -1,456 +0,0 @@
# House Plan — архивный снимок плана улучшений продукта
> **Архивировано 9 августа 2026 года.** Все актуальные пункты этого снимка
> перенесены в [GitHub Issues](https://github.com/Matysh/houseplan-card/issues)
> и Project v2. Этот файл больше не является backlog, не обновляется и хранится
> только для истории решений.
Срез: **v1.60.3-beta.2**, 9 августа 2026 года.
Этот документ — рабочий backlog, а не журнал истории. Здесь находятся только нерешённые задачи, которые подтверждаются текущим кодом, интерфейсом, пользовательским руководством или зафиксированным scope продукта. После реализации и проверки завершённый пункт удаляется из файла полностью; история пользовательских изменений остаётся в `CHANGELOG`, а архитектурные решения — в профильной документации и спецификациях.
## 1. Как читать и обновлять план
### Приоритеты
| Приоритет | Значение |
|---|---|
| **P0** | Подтверждённая потеря данных, нарушение модели доступа, опасное управление домом или блокировка основного сценария |
| **P1** | Регулярная пользовательская ловушка, недоступный основной сценарий, высокий риск регрессий или архитектурный долг, уже замедляющий развитие |
| **P2** | Существенное улучшение понятности, поддержки, производительности или качества, которое не блокирует текущую работу |
| **P3** | Условная продуктовая инициатива; начинать только после отдельного решения владельца и самостоятельного ТЗ |
На текущем срезе подтверждённых P0 нет. Если такой дефект появляется, он имеет приоритет над этой очередью и после исправления не остаётся в документе как закрытый пункт.
### Статусы открытых задач
| Статус | Значение |
|---|---|
| **Готово к ТЗ** | Проблема и ожидаемый результат понятны; перед кодом достаточно детализировать реализацию |
| **Нужно UX-решение** | Перед реализацией требуется утвердить конкретное пользовательское поведение |
| **Исследование** | Сначала нужны измерения, инвентаризация данных или прототип без изменения модели |
| **В работе** | Инициатива выполняется небольшими самостоятельными этапами; следующий этап и критерии завершения явно зафиксированы |
| **По запросу** | Инициатива соответствует scope, но не должна вытеснять основной backlog без отдельного решения |
| **ТЗ на согласовании** | Полное ТЗ подготовлено, но продуктовые решения ещё не подтверждены владельцем |
| **Готово к реализации** | ТЗ прошло ревью, блокирующих вопросов нет; код ещё не изменён |
### Обязательные правила
1. View остаётся главным ежедневным режимом; редакторы обслуживают его и не добавляют взаимодействия в просмотр без понятной пользы для членов семьи.
2. Ни одна миграция, оптимизация или очистка не удаляет пользовательские файлы и данные по косвенному признаку.
3. Новое поле настройки не добавляется без runtime-потребителя, локализации, backend validation, документации и теста совместимости.
4. Сложная геометрия развивается через чистые функции и сценарные матрицы, а не через дополнительные ветки внутри корневого render.
5. Большой рефакторинг делится на небольшие поведенчески нейтральные этапы. Переписывание приложения целиком не планируется.
6. Touch, мышь и клавиатура являются равноправными вариантами **режима просмотра**. Редакторы desktop-first: полная поддержка гарантируется для компьютера с мышью/клавиатурой, а touch-редактирование выполняется по остаточному принципу согласно `TOUCH-SUPPORT.md`. Светлая/тёмная тема и `prefers-reduced-motion` остаются общими требованиями всех поддерживаемых поверхностей.
7. Golden-image и large-house performance являются постоянными блокирующими CI-инвариантами. Новая геометрическая/визуальная поверхность расширяет golden fixture, а изменение горячего пути — performance profile или обоснование неизменности нагрузки.
## 2. Текущий срез и главные риски
| Область | Фактическое состояние | Главный оставшийся риск |
|---|---|---|
| Пользовательская ценность | Основные spatial-сценарии Home Assistant закрыты; продукт сочетает просмотр, редактирование, геометрию, свет, климат и быстрые действия | Дальнейшее добавление функций без упрощения существующего UI ухудшит осваиваемость |
| View на desktop | Информативен и визуально целостен | Часть контекста комнаты по-прежнему зависит от hover и маленькой иконки HA-зоны |
| View на touch | Устройства имеют tap/long-press сценарии | У комнаты нет полноценного tap-эквивалента hover-подсказки |
| Редакторы | Функционально глубокие, с сеткой, общей сессионной историей, стабильной основной панелью и отдельной context tray | Часть точных операций остаётся скрыта за жестами; полноценного клавиатурного управления холстом нет |
| Настройки | Все основные уровни наследования представлены | Диалоги пространства, устройства и общих настроек длинные и смешивают разные задачи |
| Состояния устройств | План, статическая карточка и live preview используют единый `ResolvedDevicePresentation`; preview показывает интеграцию, источники, состояние и fallback | Жизненный цикл найденных, скрытых, удалённых и повторно доступных привязок по-прежнему объясняется несколькими разными экранами |
| Данные и совместимость | Есть versioned migration и явная оптимизация | В модели остаются legacy-поля и внутренние ключи без публичного жизненного цикла |
| Frontend | 37 TypeScript-файлов, 32 105 физических строк | После выделения context tray `src/houseplan-card.ts` содержит 14 626 строк — 45,6% всего TypeScript; корневой монолит остаётся главным архитектурным риском |
| Типизация | Строгий TypeScript включён | В `src/` остаётся около 646 употреблений `any`, включая внутреннюю бизнес-логику, а не только границу HA |
| Проверки | Инвентаризация: 502 Node unit, 104 pure backend, 50 HA-harness и 118 browser smoke | Golden-image и base-vs-candidate large-house performance работают отдельными блокирующими CI gate; новые поверхности нужно последовательно добавлять в их fixtures |
| Документация | Подробное русское руководство, тематические документы и двуязычный changelog существуют | README/HACS-скриншоты и часть обзорного пути ещё не показывают unified Boundary, перегородки/колонны, context tray и device preview |
Главная цель ближайших циклов: **сделать уже существующую глубину понятной, доступной и дешёвой в сопровождении**, не расширяя без необходимости поверхность настроек.
## 3. Реестр открытых инициатив
| ID | Приоритет | Статус | Результат |
|---|---|---|---|
| HP-UX-01 | P1 | Нужно UX-решение | Карточка комнаты по tap/click с чистой площадью и доступными метриками |
| HP-UX-02 | P1 | Нужно UX-решение | Понятный inbox устройств: новые, размещённые, скрытые и доступные к повторному добавлению |
| HP-UX-04 | P1 | Готово к ТЗ | Разделение длинных диалогов по задачам и условное скрытие неприменимых полей |
| HP-UX-05 | P3 | По запросу | Помощь по жестам и недорогое улучшение touch-редакторов без требования паритета с desktop |
| HP-A11Y-01 | P1 | Исследование | Семантика плана для клавиатуры и screen reader; состояние читается не только цветом |
| HP-A11Y-02 | P1 | Готово к ТЗ | Единое кастомное подтверждение вместо оставшихся browser `confirm()` |
| HP-DATA-01 | P1 | В работе | Единый реестр схемы, миграций и срока жизни compatibility-полей |
| HP-ARCH-01 | P1 | В работе | Поэтапное разбиение корневого компонента и монолитных стилей |
| HP-DOC-01 | P1 | Готово к ТЗ | Актуальные скриншоты и единая пользовательская навигация по документации |
| HP-UX-06 | P2 | Нужно UX-решение | Явный room override для Glow |
| HP-UX-07 | P2 | Нужно UX-решение | Упрощённая и объяснимая система масштабов названия и карточки комнаты |
| HP-UX-08 | P2 | Нужно UX-решение | Визуальный конструктор правил иконок с advanced regex-режимом |
| HP-UX-09 | P2 | Исследование | Диагностика слишком больших подложек и безопасное уменьшение растров |
| HP-UX-10 | P2 | Нужно UX-решение | Более направляемое создание комнат на основе Floors/Areas Home Assistant |
| HP-A11Y-03 | P2 | Исследование | Клавиатурное редактирование выбранных объектов на сетке |
| HP-ENG-01 | P2 | Готово к ТЗ | Измеряемое backend coverage, строгая Python-типизация и quality-scale хвосты |
| HP-ENG-02 | P2 | Нужно UX-решение | Безопасный пользовательский support report из раздела обслуживания |
| HP-ENG-03 | P2 | Исследование | Явное решение по внутренним фильтрам и группировке устройств |
| HP-PROD-01 | P3 | По запросу | Пользовательские картинки как объекты декора |
| HP-PROD-02 | P3 | По запросу | Сводный security glance по дверям, окнам и замкам |
| HP-PROD-03 | P3 | По запросу | Пространственное отображение person/presence |
| HP-PROD-04 | P3 | По запросу | Пороговые цвета метрик карточки комнаты |
## 4. P1 — ближайший продуктовый цикл
### HP-UX-01 — карточка комнаты в режиме просмотра
**Проблема.** Чистая площадь и часть контекста комнаты доступны через hover. На touch hover отсутствует, а переход к HA area спрятан в маленькой иконке около названия. Нажатие по самому полу комнаты сейчас не даёт равнозначного результата.
**Предлагаемое поведение.** Обычный tap/click по свободной части комнаты в View открывает компактный `hp-dialog`:
- название комнаты;
- чистая площадь по той же геометрии, которая используется в hover и расчётах;
- только доступные метрики: температура, влажность, LQI и состояние света;
- явное действие «Открыть зону в Home Assistant», если у комнаты есть area;
- закрытие без навигации для комнаты без area.
Hover-подсказка на desktop остаётся быстрым просмотром. Клик по устройству, двери, окну, кнопке комнаты или другому интерактивному объекту не должен проваливаться в комнату.
**Эдж-кейсы.** Комната без area; вложенные комнаты; отверстия в чистом полу; толстые и скрытые стены; перегородки и колонны; Glow под курсором; pan до отпускания указателя; несколько комнат с общей стеной; выключенные метрики; пустое название.
**Критерии приёмки.** Площадь совпадает с hover и resize; сценарий работает мышью, touch и клавиатурой; один tap не открывает одновременно устройство и комнату; переход в HA существует только как подписанная кнопка.
### HP-UX-02 — inbox и жизненный цикл устройств
**Проблема.** Текущие «Добавить» и «Показать скрытые» технически позволяют восстановить устройство, но не объясняют, почему оно не появилось на плане, было скрыто или снова стало доступно. Автоматический фильтр, ручное скрытие и удаление выглядят как похожие состояния.
**Целевая структура.** В редакторе устройств нужен единый список с фильтрами:
| Раздел | Что показывает | Действия |
|---|---|---|
| Новые | Привязки HA, которых ещё нет на плане | Добавить, скрыть |
| На плане | Видимые маркеры | Найти на плане, редактировать |
| Скрытые | Маркеры с ручным `hidden` и первично отфильтрованные кандидаты | Показать, добавить, объяснить причину |
| Доступные снова | Удалённые привязки, которые можно добавить как новый маркер | Добавить заново |
Причина должна быть пользовательской: «скрыто вручную», «служебная сущность», «исключённый тип», «уже представлено родительским устройством». Внутренние regex и id не показываются как основное объяснение.
**Инварианты.** Просмотр списка не изменяет конфигурацию; повторное добавление не создаёт дубликат binding; никакой раздел не удаляет файл или trail без явного подтверждённого действия; одна и та же привязка получает один жизненный цикл на всех клиентах.
### HP-UX-04 — информационная архитектура диалогов
**Проблема.** Диалоги пространства, устройства и общих настроек содержат несколько независимых задач в одной длинной прокрутке. У виртуального или пассивного объекта видны поля, которые не могут дать полезный результат.
**Целевая группировка.** Это изменение интерфейса, а не модели данных.
| Диалог | Разделы |
|---|---|
| Пространство | Основа; комнаты и подписи; внешний вид; окружение |
| Устройство | Привязка; действие; состояние и свет; внешний вид; информация и файлы |
| Общие настройки | Цвета; окружение; обслуживание; о продукте |
На desktop допустимы вкладки или боковая навигация; на узком экране — последовательные секции/аккордеон. Выбранный вариант должен оставаться нативно понятным в HA и не создавать вложенный горизонтальный скролл.
**Условность полей.** Световые controls и радиус показываются только когда привязка может участвовать в on/off-свете; climate temperature — только при climate; vacuum — только при источнике координат; настройки состояния скрываются у полностью виртуального маркера; удаление и скрытие остаются видимыми действиями жизненного цикла.
**Критерии приёмки.** Переключение разделов не теряет несохранённые значения; ошибки ведут к конкретному полю; footer стабилен при любой высоте; весь сценарий проходит клавиатурой; существующие конфиги сохраняются без миграции.
### HP-A11Y-01 — доступная семантика View
**Проблема.** Комнаты являются SVG-формами с hover, устройства — кликабельными `div`, а смысл working/open/alarm часто считывается прежде всего по цвету. Общий `hp-dialog` закрывает модальную часть, но сам план остаётся слабым для клавиатуры и screen reader.
**Исследование и целевой контракт.**
1. Определить компактное дерево доступности, которое не создаёт сотни tab-stop: roving tabindex либо отдельный список «Комнаты / Устройства / Проёмы».
2. Для интерактивного объекта сформировать единый `aria-label`: имя, тип, локализованное состояние, комната и доступное действие.
3. Добавить нецветовой визуальный признак для критических состояний: тревога, открыто/разблокировано, активная механика. Он не должен превращать каждый маркер в набор badge.
4. Room card из HP-UX-01 сделать основной доступной точкой комнаты.
5. Проверить порядок фокуса после смены пространства, режима и закрытия карточки.
**Критерии приёмки.** Основные View-сценарии доступны без мыши; axe-проверка не находит базовых нарушений name/role/value и modal boundaries; при отключённом цвете состояния остаются различимы текстом или формой.
### HP-A11Y-02 — единое подтверждение опасных действий
**Проблема.** В коде остаются browser `confirm()` для удаления комнаты, пространства, плана, устройства, незавершённого контура/сегмента и для unlock. Они визуально и семантически отличаются от остальных диалогов, плохо объясняют объект операции и не дают контролировать начальный фокус.
**Решение.** Компонент `hp-confirm` поверх `hp-dialog` с promise API:
- заголовок операции, имя объекта и короткое описание последствий;
- destructive-кнопка визуально отделена от cancel;
- начальный фокус на безопасном действии;
- `Esc`, focus trap и restore focus наследуются от `hp-dialog`;
- unlock имеет отдельную предупреждающую семантику и никогда не переиспользует текст удаления;
- повторный вызов не накладывает два подтверждения и не применяет callback старого экземпляра.
**Критерий приёмки.** В runtime-коде нет прямых `confirm()`; каждый destructive path имеет browser smoke с cancel и accept; touch footer не обрезается.
### HP-DATA-01 — единая схема и lifecycle compatibility-полей
**Проблема.** Типы TypeScript, формы, runtime и Voluptuous validation синхронизируются вручную. В текущей модели видны разные классы долга:
- устаревший карточечный `tap_action`;
- read-compatibility значение display `ripple`;
- временный `settings.show_all`;
- удалённый из логики солнечных лучей `settings.weather_entity`;
- `vacuum.room_highlight` и `vacuum.segment_map`, для которых нет найденного runtime-потребителя;
- `settings.group_lights` и `settings.exclude_integrations`, влияющие на runtime без поддерживаемого пользовательского интерфейса.
**Первый этап — исследование без удаления.** Сформировать registry полей с типом, default, уровнем наследования, UI-видимостью, runtime consumer, frontend/backend enum, версией появления, migration и сроком read-compatibility. Локальный анализатор конфига должен показать, какие legacy-поля реально встречаются, не отправляя телеметрию наружу.
**Текущий этап.** Машиночитаемый registry находится в
`scripts/config-field-registry.mjs`, а read-only анализатор экспортированного
JSON — в `scripts/config-audit.mjs`. Зарегистрированы 19 известных legacy,
скрытых и compatibility-представлений, включая геометрию, декор и удалённую
погодозависимость солнечных лучей. Это ещё не каноническая схема: открытым
остаётся охват всех текущих публичных полей и автоматический
frontend/backend parity-check.
**Второй этап.**
- parity-тест сравнивает frontend и backend enum/limits;
- «Оптимизировать планы» показывает конкретную миграцию до записи;
- unknown future fields сохраняются;
- deprecated поле удаляется из публичной модели только после явно заданного окна чтения;
- внутренний ключ либо получает поддерживаемый UI, либо становится фиксированным правилом и удаляется из хранимой конфигурации.
**Критерии приёмки.** Нет поля, одновременно публичного в типе/schema и не имеющего решения «используем / мигрируем / удаляем»; новый schema drift ломает CI; открытие и сохранение старого конфига не меняет визуал без явной миграции.
### HP-ARCH-01 — декомпозиция frontend без big bang
**Проблема.** 45,6% TypeScript находится в одном `houseplan-card.ts`; там смешаны lifecycle Lit, server sync, pointer state machines, геометрические команды, все диалоги и render layers. После первого style-slice `styles.ts` содержит ещё 2 795 строк глобальных правил. Это главный множитель стоимости любых следующих функций.
**Целевые границы.**
```text
src/
app/
houseplan-card.ts # composition, HA lifecycle, top-level routing
houseplan-store.ts # normalized config/layout snapshot and revisions
navigation-controller.ts # space, mode, viewport, warm remount
editors/
plan/ # tools, pointer state, commands, toolbar
devices/ # discovery, form state, marker commands
decor/ # tools, form state, render orchestration
render/
rooms.ts
walls.ts
openings.ts
lighting.ts
devices.ts
vacuum.ts
dialogs/
room-dialog.ts
space-dialog.ts
device-dialog.ts
settings-dialog.ts
components/
hp-dialog.ts
hp-confirm.ts
hp-color-opacity.ts
```
Это направление, а не требование создать все файлы заранее. Каждый этап переносит одну законченную ответственность вместе с типами и тестами.
**Порядок безопасного разбиения.**
1. Render-only слои с явным immutable input и callbacks.
2. Модели состояния диалогов и их validation/serialization.
3. Контроллеры Plan/Devices/Decor как конечные автоматы жестов.
4. Нормализованный store и серверные ревизии.
5. Scoped styles рядом с компонентами.
**Ограничения.** Никакой новой второй модели геометрии; чистые модули `logic`, `wall-thickness`, `open-spans`, `resize`, `physical-geometry`, `sun` и `device-visual` остаются источниками истины. В новой продуктовой логике используется `unknown` и type guards вместо `any`; HA adapter boundary может быть изолированным исключением.
**Уже выделенные границы.** Отдельно существуют `hp-dialog`,
`hp-color-opacity`, цепочка `device-visual` → `device-presentation` →
`device-face`/`hp-device-preview`, integration provenance и SVG-проекция
тоннелей проёмов `render/opening-tunnels.ts`. Context tray также выделена в
`editor-secondary.ts`: модуль владеет `EditorSecondaryModel`,
`EditorToolbarGroup`, focus/animation/outside-dismiss lifecycle и стабильным
light-DOM render; её 180 строк стилей находятся в
`editor-secondary.styles.ts`. Конкретные Plan/Decor-действия остались в
корневом контроллере как typed callbacks. Все эти границы используют прежние
источники истины и не создают вторую модель состояния.
**Следующий безопасный этап.** Отдельным refactor-only slice вынести состояние,
валидацию и сериализацию одного небольшого диалога свойств, начиная с
перегородки/колонны. Не менять DOM-контракт `hp-dialog`, не смешивать перенос с
новой информационной архитектурой HP-UX-04 и не переносить заодно геометрические
команды. После подтверждения шаблона тем же способом по одному выносить более
крупные room/opening dialogs.
**Критерии приёмки.** Корневой компонент становится композиционным, ориентир — менее 3 500 строк; feature-модуль обычно не превышает 800 строк; количество `any` монотонно снижается; один и тот же полный smoke-набор проходит до и после каждого этапа; визуальных изменений в refactor-only PR нет.
### HP-DOC-01 — документация текущего пользовательского опыта
**Проблема.** User guide подробный, но обзорные README-скриншоты и часть формулировок отражают более ранний состав редакторов. Пользователь сначала видит именно README/HACS, поэтому несоответствие ухудшает onboarding.
**Пакет.**
- переснять на синтетическом доме View, создание пространства, комнату, Plan editor с context tray, Devices editor с preview, Background editor и карточку устройства;
- коротко объяснить различие «Контур комнаты / Перегородка / Колонна / Граница / Проём»;
- добавить таблицу жестов mouse/touch/keyboard;
- провести проверку внутренних ссылок и терминов RU/EN;
- README оставить обзорным, детали вести в `USER-GUIDE.ru.md` и тематические документы;
- для нескольких карточек с разными стартовыми пространствами явно зафиксировать поддерживаемый сценарий и ограничения.
**Критерии приёмки.** Ни один screenshot не показывает отсутствующую кнопку или старое название; основной путь от установки до первой комнаты читается без changelog; ссылки проверяются в CI.
## 5. P2 — следующий слой улучшений
### HP-UX-06 — явный Glow на уровне комнаты
Сейчас пространство может выбрать Glow, а комната — наследовать его или переопределить на `none/lqi/light/temp`. Комната не может зафиксировать Glow, если пространство позднее переключат в другой режим.
Нужно добавить `glow` в room override без изменения семантики наследования. До кода требуется решить, участвует ли явно отключённая от Glow соседняя комната в физическом распространении света или только не рисует базовое затемнение. Решение фиксируется одной матрицей для fill, clip и переходов через виртуальные/дверные границы.
### HP-UX-07 — одна объяснимая система масштабов комнаты
Сейчас итоговый размер образуют масштаб карточки пространства, отдельные `name_scale`/`label_scale` комнаты и сохранённый drag-scale карточки. Система мощная, но пользователь не видит итоговый коэффициент и способ сброса конкретного уровня.
Цель: оставить один default пространства и явные room overrides для названия и метрик. Визуальный resize либо редактирует те же числа, либо показывает фактическое значение и кнопку Reset. Legacy layout-scale читается без визуального скачка и преобразуется только явной оптимизацией.
### HP-UX-08 — конструктор правил иконок
Обычный режим должен предлагать поля «домен», `device_class`, слова в имени/модели и итоговую иконку. Порядок правил остаётся видимым, тестовое устройство показывает первое сработавшее правило и причину. Regex сохраняется как advanced-режим с валидацией и предупреждением о приоритете; существующие regex не переписываются автоматически.
### HP-UX-09 — большие подложки
Добавить локальную диагностику разрешения, decoded memory и ожидаемого размера перед загрузкой PNG/JPEG/WebP. Для больших растров предложить безопасную копию с уменьшением, сохраняя оригинал до подтверждения успешной загрузки. SVG не растеризуется автоматически. Порог определяется после тестов на типичных wall-tablet/desktop устройствах и документируется вместе с file-size limit.
### HP-UX-10 — Floors/Areas как направляемый onboarding
При создании комнат список area должен учитывать выбранный/импортированный floor, первым показывать неиспользованные зоны этого этажа и предлагать их имя. Нужен небольшой progress «размечено N из M зон» и явный путь для комнаты без zone. Компонент по-прежнему не переименовывает и не переносит HA area — он только читает registry.
### HP-A11Y-03 — клавиатурное редактирование выбранных объектов
Первый этап не обязан рисовать произвольный polygon только клавиатурой. Достаточно сделать выбор и точные операции:
- roving focus между объектами текущего слоя;
- Enter — свойства, Delete — только выбранный объект с подтверждением там, где оно требуется;
- стрелки — на один узел сетки, модификатор — на согласованный увеличенный шаг;
- Escape — отмена текущего жеста/выбора;
- доступное объявление координат, длины, угла и результата Undo.
До реализации нужен прототип конфликтов со scroll, pan и текстовыми полями.
### HP-ENG-01 — измеряемое инженерное качество backend
- подключить `pytest-cov` и зафиксировать честное покрытие backend, затем довести его минимум до 95% по исполняемым строкам;
- настроить строгую Python-типизацию по модулям, начиная с validation/store/websocket boundaries;
- добавить lint/format gate без массового шумного rewrite;
- закрыть применимые пункты quality scale: troubleshooting и examples;
- пользовательские ошибки WebSocket, которые доходят до UI, должны иметь стабильный code и локализованное сообщение.
### HP-ENG-02 — support report без персональных данных
В «Обслуживании» добавить действие «Скопировать отчёт для поддержки». Отчёт содержит версии card/integration/HA, модель данных, количества пространств/комнат/стен/устройств, состояние ревизий, результаты read-only validation и активные Repairs. По умолчанию исключаются имена, entity/device id, URLs, пути файлов, описания и координаты дома. Перед копированием пользователь видит полный текст.
### HP-ENG-03 — фильтры и группировка как явное решение
Провести инвентаризацию `exclude_integrations` и `group_lights` на реальных локальных конфигах. Для каждого ключа выбрать одно из двух:
1. поддерживаемая advanced-настройка в inbox с понятным эффектом и default;
2. фиксированное продуктовой логикой поведение без хранимого ключа.
Нельзя оставлять третий вариант — скрытую настройку, которая влияет на результат, но не видна пользователю и не имеет migration policy.
## 6. P3 — инициативы только по отдельному решению
### HP-UX-05 — помощь по жестам и best-effort touch-редакторы
**Статус поддержки.** Редакторы являются desktop-first инструментами администратора. Полный touch-паритет не является целью или критерием релиза. Пользователям рекомендуется редактировать планы на компьютере; неудобная, частичная или отсутствующая touch-операция допустима, если View и безопасность данных не страдают.
**Проблема.** Даже в эталонной desktop-среде скрытые жесты (`double click`, `Ctrl`/`Cmd`, `Shift`, конечные ручки и тонкие объекты) не всегда объясняются в момент работы. Некоторые touch-улучшения можно получить дёшево, не усложняя модель взаимодействия.
**Что изменить.**
- интерактивные ручки и icon-only кнопки получают увеличенную невидимую область там, где это не создаёт конфликтов геометрии или desktop-ввода; размер 44×44 является ориентиром, а не обязательным контрактом всех редакторов;
- линии, тонкие контуры и концы сегментов используют scale-independent hit area;
- активный инструмент показывает одну короткую контекстную подсказку, а не постоянную стену текста;
- Help overlay собирает `Ctrl`/`Cmd`-замыкание, `Shift`-углы, double click свойств, Undo/Redo, pan/zoom и Erase;
- подсказка различает keyboard modifier по платформе;
- overlay можно закрыть навсегда и повторно открыть из панели;
- дорогие precision-жесты не переносятся на touch только ради формального паритета; допустимо скрыть/отключить операцию и рекомендовать desktop;
- best effort не позволяет сохранять случайную геометрию после pinch, `pointercancel` или второго касания и не позволяет ломать выход обратно в View.
**Эдж-кейсы.** Пересекающиеся hit-зоны; две близкие ручки; pan/pinch над выбранным объектом; stylus; zoom 0.4× и крупный zoom; скрытая панель; `prefers-reduced-motion`; гибридный ноутбук; планшет с подключённой мышью.
**Критерий.** Desktop-подсказки и рабочие операции остаются понятными и полными. Touch-улучшения принимаются отдельно по конкретным сценариям; общая функциональная эквивалентность редакторов не проверяется.
### HP-PROD-01 — картинки как объекты декора
До UI нужен отдельный lifecycle-дизайн: upload, reuse, copy-on-write, ссылки между пространствами, квоты, замена, экспорт, явное удаление и защита от файлов-сирот. Геометрический transform может использовать существующий контракт декора, но серверное хранение нельзя строить как побочный вариант вложений устройства.
### HP-PROD-02 — security glance
Один компактный статус дома: закрыты ли двери/окна и заперты ли замки, с раскрытием списка проблемных точек. Это только просмотр и навигация; новый быстрый массовый unlock запрещён.
### HP-PROD-03 — person/presence на плане
Нужно сначала выбрать модель приватности и источник: HA person area, device tracker или присутствие комнаты. Не смешивать длительное присутствие с коротким событием движения и не рисовать ложную точную позицию, если HA знает только area.
### HP-PROD-04 — пороговые цвета метрик комнаты
Температура, влажность и LQI в карточке комнаты могут иметь настраиваемые диапазоны, но настройка не должна дублировать существующие fill thresholds. Сначала определить общую модель порогов и наследования.
## 7. Рекомендуемый порядок выполнения
| Пакет | Состав | Почему так |
|---|---|---|
| **A. Техническая основа следующего цикла** | следующий slice HP-ARCH-01 и parity-этап HP-DATA-01 | Golden/performance gates уже готовы; теперь уменьшаем стоимость изменений и риск schema drift без нового поведения |
| **B. Понятный и доступный View** | HP-UX-01, HP-A11Y-01, HP-A11Y-02 | Максимальная ежедневная польза и обязательный desktop/touch/keyboard-паритет просмотра |
| **C. Понятный жизненный цикл и настройка устройств** | HP-UX-02, HP-UX-04 | Live preview уже решает объяснение визуала; остаются discovery/inbox и структура длинного диалога |
| **D. Документация текущего продукта** | HP-DOC-01 параллельно пакетам B–C | README/HACS должны показывать уже существующий продукт, а не ждать следующего большого релиза |
| **E. Desktop-редакторы без скрытых ловушек** | HP-A11Y-03; HP-UX-05 только по запросу или когда улучшение дёшево | Полнота и точность редакторов гарантируются на desktop; touch-паритет не должен раздувать сложность |
| **F. Второй приоритет** | HP-UX-06…10, HP-ENG-01…03 | Брать по одному после стабилизации соответствующего слоя |
Архитектурное разбиение и schema registry — не отдельная «заморозка продукта». Каждый пользовательский пакет должен оставлять затронутый участок более модульным и лучше проверяемым, чем до него. Golden-image и performance уже являются постоянной инфраструктурой, а не открытыми инициативами.
## 8. Матрицы качества
### Обязательные сценарные матрицы
| Матрица | Измерения |
|---|---|
| Устройство | binding kind × domain × device class × state × availability × display × controls |
| Свет | auto/explicit/control source × hidden/removed × fill mode × opening × wall/partition/column |
| Комната | area/no area × nested/shared × thick wall × fill override × touch/hover/keyboard |
| Геометрия | real/virtual × thickness A/B × corner/T/X × opening × resize × Undo/Redo |
| Декор | kind × solid/dashed × fill/stroke × transform × erase/select × zoom |
| Ввод View | mouse × touch × stylus × keyboard × View/kiosk; touch является обязательной поддерживаемой поверхностью |
| Ввод редакторов | desktop mouse/keyboard × Plan/Devices/Background; touch/stylus — safety floor и только отдельно обещанные сценарии |
| Миграция | legacy field × open/save × optimize × undo optimize × future unknown field |
| Адаптивность | RU/EN × light/dark × narrow/medium/wide × reduced motion |
| Производительность | base SHA/candidate × first render/space switch/state update/resize/pan/dialog × Long Task/heap/cache growth |
### Метрики направления
| Метрика | Цель |
|---|---|
| Обычное действие, теряющее данные без явного решения | 0 |
| Публичное поле без runtime consumer или migration policy | 0 |
| Ключевая информация, доступная только через hover | 0 |
| Опасное действие через native `confirm()` | 0 |
| Критический статус, различимый только цветом | 0 |
| Новый `any` вне изолированного HA adapter boundary | 0 |
| Расхождение frontend/backend enum и limits | 0 в CI |
| Визуальная правка геометрии без соответствующего fixture | 0 |
| Изменение горячего пути без large-house comparison либо явного обоснования | 0 |
| Размер `houseplan-card.ts` после декомпозиции | Ориентир <3 500 физических строк |
| Backend coverage после HP-ENG-01 | ≥95% исполняемых строк |
## 9. Release- и документационный процесс
Рабочий процесс владельца остаётся источником истины:
- обычные локальные правки вносятся без тестов и без коммитов;
- перед pre-release выполняются production build и минимально необходимые целевые unit/smoke проверки изменённых поверхностей;
- перед stable release выполняется полный frontend/backend/browser gate;
- текущие количества тестов берутся из `npm run inventory`, а не копируются навсегда в статусные документы;
- пользовательское изменение одновременно обновляет RU/EN changelog и соответствующую таблицу поведения/руководство;
- release body короткий: только значимые функции, остальное группируется как «Мелкие исправления и улучшения» / “Small fixes and improvements”, плюс ссылки на RU и EN changelog.
Сам этот файл обновляется иначе, чем changelog: завершённая инициатива удаляется, а не отмечается галочкой. Если задача утратила смысл из-за другого решения, она также удаляется с объяснением в ADR/spec или changelog соответствующего изменения.
## 10. Границы плана
В этот backlog не входят:
- 3D/isometric/фотореалистичное моделирование;
- редактор автоматизаций, сценариев и уведомлений;
- администрирование entity/device/area registry Home Assistant;
- история, графики, energy analytics, camera streams и медиапульт;
- общий dashboard framework;
- облачное хранение или телеметрия пользовательских планов.
Новая идея попадает сюда только если усиливает spatial glance, безопасное быстрое действие, создание/поддержку плана или качество реализации этих сценариев.
+1 -2
View File
@@ -6,8 +6,7 @@ flexible (options instead of assumptions), and measured against the official
**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 their labels
(`PROCESS.md` §9); this file keeps historical engineering direction. The original market rationale is archived at
`legacy/docs/PRODUCT-2026-07-05.md`.
(`PROCESS.md` §9); this file keeps historical engineering direction. The original market rationale lives only in git history (#678).
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.
@@ -1,154 +0,0 @@
# Audit — functional integrity & gaps
> Snapshot: **2026-08-05**, product **v1.58.0**.
> Cross-check of claimed surface (README / STATUS / SCOPE) against code and
> tests. Mission lens: SCOPE.md — *spatial at-a-glance + quick act*.
## 1. Mission fit
SCOPE personas:
| Persona | Primary mode | Fit |
|---|---|---|
| Home admin | Plan / Devices / Background editors | Strong — full geometry + marker tooling |
| Household members | View | Strong — tap toggles, room cards, glow/climate |
| Guests / kiosk | View + `kiosk: true` | Strong — editors blocked, swipe/cycle |
Jobs J1–J7 are marked **Closed** in SCOPE; J4 (onboarding) still **partial**
(floors-import exists; registry-driven room *suggestions* do not). That matches
code reality.
**Systematicity grade: high.** Features hang off a coherent model:
- spaces → rooms (polygons) → derived walls → openings
- markers bound to device/entity/virtual → layout points
- settings tiers: global → space → room → device
- View is the product; editors are admin-colored frames (UX-MODES)
Excess features (PDF manuals, virtual devices, LQI) are consciously **kept /
frozen** rather than deleted — unusual and correct for a shipping tool.
## 2. Claim ↔ code matrix
| Feature area | Claimed | Implemented | Tested | Notes |
|---|---|---|---|---|
| One HACS package (integration + card) | ✓ | ✓ | CI hacs/hassfest | Matches |
| GUI room markup (no SVG/YAML) | ✓ | ✓ | smokes + logic units | Matches |
| Doors/windows + lock invariant | ✓ | ✓ | smoke_lock_*, logic | CR-1 held |
| Islands / merge / split / open_to | ✓ | ✓ | units + smokes | Matches |
| Room resize | ✓ | `resize.ts` | units + smokes | Matches |
| Decor + backdrop transform | ✓ | ✓ (v1.58) | backdrop tests/smokes | Paper = room contours |
| Infinite / square canvas | ✓ | ✓ | canvas smokes | Matches CANVAS.md |
| Align-to-grid | ✓ | `align-grid.ts` | units | Matches |
| Auto devices by HA area | ✓ | `devices.ts` | units | Matches |
| Explicit hide-from-plan | ✓ | ✓ | smokes | FILTERING.md |
| Editable icon rules | ✓ | ✓ | — | Matches |
| Tap: info / more-info / toggle / run / cover | ✓ | ✓ | logic + smokes | Security model intact |
| Glow + door sectors | ✓ | ✓ | smokes | Island light block = known limit |
| Temp / LQI / light fills | ✓ | ✓ | units/smokes | Matches |
| Room cards + per-room settings | ✓ | ✓ | smokes | Matches |
| Kiosk | ✓ | ✓ | smokes | Matches |
| Vacuums + server trails | ✓ | `vacuum.ts` + `trails.py` | units + smokes | Display-only (intentional) |
| Sun / daynight / wedges | ✓ | `sun.ts` | units + smokes | Matches |
| `houseplan-space-card` | ✓ | ✓ | smokes | Matches |
| Signed content + SVG sandbox | ✓ | ✓ | HA tests + smokes | Matches |
| en/ru i18n | ✓ | ✓ | key-parity test | Matches |
| Floors-import wizard | ✓ | ✓ | — | Exists; suggestions gap below |
| Registry room suggestions | PRODUCT / J4 | **Partial / missing** | — | Gap |
| Music notes / TV ripples | UX backlog | **Absent** | — | Intentional not-planned |
| Furniture / wall CAD | competitor feature | **Absent** | — | Non-goal |
| Vacuum commands | user ask often | **Absent** | — | VACUUM.md non-goal |
| Cloud sync | — | **Absent** | — | Non-goal |
**Integrity conclusion:** marketed surface and shipped surface align unusually
well. The main integrity risks are **docs drift** (ARCHITECTURE/UX-MODES/PRODUCT)
and **auth UI↔API default mismatch** (see AUDIT-QUALITY), not phantom features.
## 3. Cross-cutting integrity themes
### 3.1 Coordinate & geometry story — coherent after evolution
Legacy fixed pixel canvas → normalized square → infinite canvas with bounds.
Migrations + `geometry/repair` + dual-store `geom_pending` show the team treats
coordinate changes as product-critical (correct). Frontend `space-geometry.ts`
and backend `validation.py` intentionally mirror limits (±5000) — two sources of
truth, but tested.
### 3.2 Security story — mostly coherent
| Surface | Policy | Coherent? |
|---|---|---|
| Lock / alarm from plan icon | Never actuate | Yes — tested |
| Lock via door-card button | Explicit labeled control | Yes — SCOPE CR-1 |
| SVG plans | Sandbox CSP + signing | Yes |
| Config/layout writes | `may_write` | **Split** with UI admin gate |
| `run` tap | automation/script/scene only + optional confirm | Yes |
### 3.3 Multi-client story — mostly coherent
Config writes: CAS + write chain. Layout point updates: rev bumps but **no**
conflict surface — fine for one editor, weak for two tablets dragging at once.
### 3.4 Filtering story — cleaned up
Old hardcoded “dacha DNA” filters → editable icon rules + explicit hide flags
(v1.51). Documented in FILTERING.md. Good systematic cleanup.
## 4. Gaps (build only with intent)
### In-mission polish (SCOPE already lists most)
| Gap | Persona impact | Notes |
|---|---|---|
| Registry-driven room suggestions | Admin first-run | J4 partial; biggest *product* unlock left |
| Touch ergonomics of editors | Admin on tablet | Partial |
| Value-display richness | Household | Partial |
| a11y beyond `prefers-reduced-motion` | All | Keyboard/ARIA weak |
| Plan-level security glance (“N open / all locked”) | Household / kiosk | SCOPE known gap |
| Person / presence in rooms | Household | SCOPE known gap; presence ripples exist at marker level |
| Threshold colouring for room metrics | Household | SCOPE known gap |
| README / screenshot lag vs redesign | Adoption | Distribution, not function |
### Engineering gaps that feel like product bugs
| Gap | Why it matters |
|---|---|
| Default `admin_only=False` while UI hides editors from non-admins | Non-admin can still mutate via raw WS — surprise vs household persona |
| Opening-measure magnet placement flake under pinned Chromium | One smoke red; pixel-precision path |
| Demo.html needs hass nudge / F5 for icons | Harness quirk; confuses manual demo in a raw browser |
### Explicit non-gaps (do not “fix” by building these)
- 3D / glb
- Furniture / wall drawing (easy-floorplan territory)
- Vacuum start/zone commands
- History graphs, cameras, energy
- Cloud collaboration
- Becoming a general dashboard framework
## 5. Systematicity scorecard
| Question | Answer |
|---|---|
| Is there one mental model? | Yes — spaces/rooms/markers/settings tiers |
| Do modes prevent tool leakage? | Mostly — UX-MODES + kiosk hard-block; View is default |
| Are overrides predictable? | Yes — more specific settings win |
| Do security rules have a single owner? | Tap: `resolveTapAction`. Write: `may_write` (UI duplicate — debt) |
| Are frozen excesses documented? | Yes — SCOPE excess audit |
| Is the roadmap still pointing at the mission? | Phase 8–10 are quality/distribution; Phase 9 leftovers are registry/docs — aligned |
**Grade: A− for functional systematicity.** The product feels designed, not
accreted — with the structural exception of the Lit shell size (quality debt,
not a feature-model debt).
## 6. Suggested integrity checks for future agents
Before merging a feature:
1. Does it serve J1–J7 or a SCOPE “known gap”? If neither → reject or rewrite.
2. Does it introduce a second policy for taps, writes, or file deletion? → fold
into `resolveTapAction` / `may_write` / collection rules.
3. Does it need a migration? → dual-store / repair path, not silent reshape.
4. Is it tested at the right layer? Pure math → `npm test` / pytest; pointer UX
→ smoke with `check`/`finish`; never a smoke that always exits 0.
5. Dual CHANGELOG + STATUS/ARCHITECTURE/TESTING updates in the **same** commit.
-112
View File
@@ -1,112 +0,0 @@
# Audit — market potential & competitors
> Snapshot: **2026-08-05**. Star counts via GitHub API the same day.
> Supersedes the competitor table in `PRODUCT.md` (2026-07-05) until that
> file is rewritten. Re-verify numbers before any public claim.
## 1. Demand shape
| Signal | Evidence |
|---|---|
| Persistent pain | HA Community “Floorplan” category; “100% Floorplan UI” mega-thread (500k+ views historically) |
| 2025–2026 wave | Multiple GUI floorplan projects launched within months (easy-floorplan May 2026, Padraigggs Mar 2026, House Plan Jul 2026, plus older SVG/YAML tools) |
| Audience | Enthusiasts with wall tablets / panel dashboards — **niche but sticky** |
| Adjacent proof | Bubble Card (~4.4k★) shows polished GUI Lovelace cards can go quasi-mainstream; ha-floorplan (~1.6k★) with a hostile workflow shows a demand floor for *spatial* UIs |
**Verdict:** demand is real. The bottleneck is not “do people want a plan?” —
it is “which GUI-first story wins attention before HA core ships something
spatial.”
## 2. Competitive landscape (2026-08-05)
| Project | ★ | Updated | Approach | Overlaps us | Where we still win | Where they win |
|---|---:|---|---|---|---|---|
| [ha-floorplan](https://github.com/ExperienceLovelace/ha-floorplan) | **1584** | 2026-07 | Hand SVG + YAML/TS rules | Visualization power | Zero Inkscape/YAML barrier; shared server config | Max customization, mature ecosystem, contributors |
| [easy-floorplan](https://github.com/nicosandller/easy-floorplan) | **430** | 2026-08-04 | In-card draw walls/furniture/devices | **Direct peer** — GUI, no YAML | HA **integration** + `.storage`, area-bound rooms, auto devices, vacuums/sun/glow/kiosk, quality-scale CI | Furniture drawing, faster growth, press (DE blogs), simpler “draw a house” story |
| native `picture-elements` | built-in | — | % coords in YAML | Overlay icons on image | Editor + zones + shared layout | Zero install, official |
| native Areas / Home dashboard | built-in | evolving | Grid by area/floor | “At a glance” home | **Spatial** plan | First-party discoverability |
| [Padraigggs interactive floorplan](https://github.com/Padraiggg/Padraigggs-ha-interactive-floorplan) | 42 | 2026-04 | Editor + viewer cards, push to dashboard YAML | GUI editor | Server store, room polygons↔areas, overlays | Polygon light zones, camera animations, YAML export |
| [zigbee-floorplan-card](https://github.com/TheLarsinator/zigbee-floorplan-card) | 73 | 2026-04 | LQI over image | LQI overlay | Full product, not single-purpose | Narrow clarity |
| Dwains / Bubble Card | 2k / 4.4k | — | Dashboards / card kits | GUI quality bar | Spatial niche | Distribution, brand |
| **House Plan** (us) | **21** | 2026-08-05 | Integration + card, room markup, server layout | — | See §3 | Traction, HACS default pending |
### Critical change since PRODUCT.md (2026-07-05)
`easy-floorplan` was listed at **11★ / immature**. One month later it is
**≈430★**, actively released (v0.8.x), and getting third-party blog coverage.
That collapses the old claim that the GUI-floorplan niche is “currently
unoccupied.” The niche is now a **race**, and they are ahead on attention.
House Plan is **not** the same product: we refuse furniture/wall CAD
(SCOPE / ROADMAP non-goal) and instead ship a **storage integration**,
area-linked rooms, multi-client layout, and curated overlays. That moat is
real — but only if users *find* us and understand the difference in one
demo GIF.
## 3. Our moat (still valid)
Nobody else currently combines all of:
1. **Server-side config** — HA integration, `.storage`, optimistic `expected_rev`,
live multi-client sync, survives dashboard YAML edits.
2. **In-card room polygon editor** bound to HA **areas** → auto device placement.
3. **Curated overlays** — glow pools, temp/LQI fills, sun wedges, vacuum trails,
cover morph, lock invariant.
4. **Operational maturity** — quality_scale.yaml, 4-layer tests, signed content +
SVG CSP, diagnostics/repairs/system_health, dual en/ru docs.
Card-only peers store config in Lovelace YAML (or push into it). That is fine
for one admin laptop; it is weaker for family tablets and multi-user homes —
exactly our persona split in SCOPE.md.
## 4. Potential (honest)
| Horizon | Realistic outcome | Depends on |
|---|---|---|
| Near (HACS default + demo assets) | Low hundreds of ★; Telegram + forum traction | #9004 merge, GIF/video, EN forum/Reddit posts |
| Medium (12 months of polish + registry depth) | Contender in the GUI-floorplan shortlist; maybe 0.5–1.5k★ if narrative sticks | Differentiation messaging vs easy-floorplan; onboarding magic |
| Ceiling | Unlikely to dethrone ha-floorplan’s power-user base; unlikely Bubble-scale | Niche size + single maintainer |
**Usefulness:** high for the target personas (admin builds once; household/kiosk
use View). **Commercialization:** none intended (MIT, local-first) — success =
installs and unpaid maintenance load.
## 5. Strategic risks
| Risk | Severity | Mitigation |
|---|---|---|
| easy-floorplan owns the “no YAML floorplan” mindshare | **High** | Sharpen README differentiation (server sync, areas, overlays); ship demo GIF now |
| HA core ships a native spatial plan | **High (latent)** | Deepen floors/areas registry integration; speed of iteration |
| HACS default #9004 stuck for months | **Medium** | Custom-repo path works; social proof before merge |
| Single-maintainer bus factor | **Medium** | Docs + tests already strong; avoid feature sprawl |
| Frontend API churn (`hass` internals) | **Medium** | Minimal surface; CI against current HA in harness |
| Scope creep toward furniture CAD | **Self-inflicted** | SCOPE non-goals — do not chase easy-floorplan feature-for-feature |
## 6. Positioning recommendation
**One sentence:** *House Plan is the shared, area-aware live map of your Home
Assistant home — not a drawing app for furniture.*
Lead with: multi-device sync, bind room→area→devices appear, glow/climate/sun/
vacuums, kiosk for wall tablets. Acknowledge: if you want to *draw walls and
sofas from scratch*, use easy-floorplan; if you have a plan image and a real HA
registry, use House Plan.
## 7. Distribution checklist (status)
| Lever | State (2026-08-05) |
|---|---|
| Public demo | **Live** — https://demo.houseplan.tech (`demo`/`demo`) |
| Dev stand | https://dev.houseplan.tech |
| Telegram | https://t.me/ha_houseplan |
| HACS custom install | Works |
| HACS default | **Queued** — hacs/default#9004 open since 2026-07-06 |
| Demo GIF / video in README | Still a ROADMAP Phase 10 open item |
| Forum Floorplan + Reddit posts | Drafts exist off-repo; posting still open |
| Stars | **21** — traction problem, not a product-depth problem |
## 8. What to refresh next
When stars or competitor maturity move materially, update **this file first**,
then sync the table in `PRODUCT.md`. Do not leave PRODUCT as the only market
doc — it already went a month stale while easy-floorplan 40×’d.
-132
View File
@@ -1,132 +0,0 @@
# Audit — implementation quality
> Snapshot: **2026-08-05**, product **v1.58.0**.
> Severity: **critical / major / minor / nit**. No critical RCE found under
> normal HA session auth.
## 1. Architecture snapshot
```
src/ Lit 3 Lovelace cards (houseplan + space-card)
houseplan-card.ts ~8691 LOC — orchestration god-object
logic.ts / devices.ts / … extracted pure modules (good)
styles.ts ~2223 LOC CSS-in-JS
custom_components/houseplan/ HA integration (storage, WS, HTTP, trails)
dist/ + frontend/ committed bundle (CI byte-compares)
demo/ Playwright harness + smoke_*.mjs
tests_backend/ pure + HA-harness pytest
```
Bundle: **≈449 KB** raw / **≈128 KB** gzip — acceptable for a full editor+viewer.
## 2. What is strong
### Backend
- Single write-auth helper (`auth.may_write`) with **fail-closed** when entry
missing (audit B2 lesson documented in the module).
- Voluptuous validation with **cross-language option-list tests** that parse
`DISPLAY_MODES` / `TAP_ACTIONS` from `logic.ts` — rare and valuable.
- Config CAS via `expected_rev`; plan COW + quotas; never-delete-on-inference
file policy (SCOPE).
- Geometry migration with durable `geom_pending` intent (HP-1490 class).
- Content HTTP: `requires_auth`, signed URLs for `<image href>`, SVG CSP sandbox.
- Diagnostics / repairs / system_health present; `quality_scale.yaml` mostly
**honest** (`test-coverage`, `strict-typing`, docs todos marked todo).
### Frontend (extracted brain)
- Tap security model in `resolveTapAction` — locks/alarms never toggle from the
plan; cover garage/door/gate guarded; `run` limited to automation/script/scene.
- Pure geometry / devices / sun / vacuum / resize / align-grid / signing modules
with solid `node:test` coverage (~270 tests).
- Shared `config-store` + space-geometry/render for `houseplan-space-card`.
- Optimistic UI without rollback is **documented intentional** (ARCHITECTURE).
### Process
- Four test layers + smoke policy (“`[manual]` must have a failing auto check”).
- Dual CHANGELOG (en/ru), SCOPE guard rail, CONTRIBUTING five-minute loop.
- CI: hacs + hassfest + frontend + backend + smoke job.
## 3. Findings (ranked)
| # | Sev | Finding | Where |
|---|---|---|---|
| 1 | **major** | God-object card: ~8691 LOC, ~359 methods, ~125 private fields; `render()` ~382 lines. Smokes-only coverage for orchestration. | `src/houseplan-card.ts` |
| 2 | **major** | `strict: true` hollowed by `noImplicitAny: false`; ~200 `any` sites in the card alone; `hass: any` everywhere | `tsconfig.json`, card / devices / logic |
| 3 | **major** | Write-policy split: UI `_canEdit` is always admin-gated; API default `admin_only=False` lets **any** authenticated user write via WS/HTTP | `houseplan-card.ts` ≈L247–249, `auth.py` |
| 4 | **major** | `_canEdit` fails **open** when `hass.user` is missing (`is_admin !== false`) | card ≈L248 |
| 5 | **major** | `styles.ts` ~2223 LOC — second maintainability sink; untested | `src/styles.ts` |
| 6 | **minor** | `layout/update` has no `expected_rev` — multi-tablet last-writer-wins per point | `websocket_api.py` |
| 7 | **minor** | Marker `binding` is bare `str`; `ripple_color` not hex-matched; decor `w`/`h` allow negatives via `_NORM` | `validation.py` |
| 8 | **minor** | Card-level `tap_action` / `resolveTapAction` cardDefault is dead / ignored — confusing API surface | `types.ts`, click path |
| 9 | **minor** | Store `_async_migrate_func` is a no-op; real migrations live ad-hoc in setup | `store.py`, `__init__.py` |
| 10 | **nit** | `quality_scale.yaml` cites `test_config_flow.py`; file is `test_ha_config_flow.py` | quality_scale.yaml |
| 11 | **nit** | Duplicated `fireEvent` / `navigate` in card + space-card | both files |
| — | positive | Forbidden-domain tap model + confirm + cover guards | `logic.ts` |
| — | positive | Signed content + SVG sandbox | `http_api.py`, `signing.ts` |
**No critical** auth bypass of HA sessions found. Closest systemic issue is
finding #3 (API openness vs UI) under the default options.
## 4. TypeScript & tooling
| Item | State |
|---|---|
| `tsc --noEmit` in build | Yes — correct (rollup TS plugin can warn-and-ship) |
| ESLint / Prettier | **Absent** |
| `noImplicitAny` | **Off** |
| Frontend unit of the Lit class | **None** |
| mypy strict (backend) | quality_scale **todo** |
## 5. Test map
| Layer | What it proves | Gap |
|---|---|---|
| `npm test` (~270) | Pure logic, i18n parity, tap security, geometry | Not the card shell |
| `pytest` pure | validation.py without HA | — |
| HA-harness (py≥3.13) | setup, WS races, auth, upload, geometry repair | Coverage % unmeasured |
| `demo/smoke_*.mjs` (~100) | Real pointer/UI against fake hass | Pixel flakes (`smoke_opening_measure`); no Safari/Companion matrix |
Human checklist in TESTING.md last full self-run recorded at **v1.21.1** while
product is at **v1.58.0** — auto net grew; documented human pass did not.
## 6. Docs drift (quality of truth)
| Doc | Drift |
|---|---|
| `ARCHITECTURE.md` intro | Still mentions old `src/data/house.ts` / 1489×1053 era in places; square/infinite canvas sections newer |
| `UX-MODES.md` header | Still says “No code has been changed yet” — Phase 11 shipped |
| `PRODUCT.md` competitor table | **Stale** — easy-floorplan 11→430★ (see AUDIT-MARKET) |
| `STATUS.md` | Generally current as of 2026-08-04 |
Docs discipline is a project strength; these drifts are fixable and should be
treated as debt, not ignored.
## 7. Top 10 technical debt (actionable)
1. Split `houseplan-card.ts` into shell + Plan/Devices/Decor editors + dialogs.
2. Turn on `noImplicitAny` incrementally; type a thin `Hass` surface.
3. Align write policy: wire UI to `admin_only`, or default `admin_only=True`.
4. Fail closed on missing `hass.user`.
5. Add CAS / expected_rev to `layout/update` (or document single-writer assumption).
6. Split `styles.ts` by feature surface.
7. Tighten MARKER_SCHEMA (`binding`, colors, decor extents, space ids).
8. Unit-test orchestration hotspots (`_clickDevice`, write-chain conflicts, modes).
9. Real Store migrations or document setup-time migrations as the only path.
10. Remove or re-wire dead card-level `tap_action` / cardDefault.
Detailed priority and sequencing: [`AUDIT-RECOMMENDATIONS.md`](AUDIT-RECOMMENDATIONS.md).
## 8. Quality-scale honesty check
| Claim | Audit view |
|---|---|
| Bronze structural items `done` | Accurate |
| Silver unloading / owner `done` | Accurate |
| Gold diagnostics / repairs `done` | Accurate |
| `test-coverage` / `strict-typing` / docs examples `todo` | Accurate — keep them todo until measured |
| `config-flow-test-coverage` path typo | Nit — fix filename in yaml |
Overall: self-assessment is trustworthy; do not mark coverage done without a number.
@@ -1,99 +0,0 @@
# Audit — prioritized recommendations
> Snapshot: **2026-08-05**. Priorities for humans and agents.
> P0 = do soon (risk or traction). P1 = next engineering cycle.
> P2 = important but schedulable. P3 = niceties / when touching adjacent code.
>
> Status column: update in-place when an item ships (`done YYYY-MM-DD` or
> `dropped — reason`).
## P0 — traction & trust (this month)
| ID | Action | Why | Status |
|---|---|---|---|
| P0-1 | Ship **demo GIF/video** on README (real product motion: glow + tap light + kiosk) | Biggest adoption lever; ROADMAP Phase 10; easy-floorplan is winning attention without our counter-demo | open |
| P0-2 | Publish EN **forum Floorplan + Reddit** posts (drafts exist off-repo) | Social proof before/while HACS #9004 waits | open |
| P0-3 | Refresh **README differentiation** vs easy-floorplan (server sync, areas→devices, overlays — not furniture CAD) | PRODUCT table is a month stale; narrative gap is urgent | **done 2026-08-05** (README + README.ru) |
| P0-4 | Align **write policy**: either default `admin_only=True` **or** drive `_canEdit` from the same option; fail closed if `hass.user` missing | UI↔API inconsistency; household persona assumption | **done 2026-08-05** (`can_write` on config/get, default admin_only on, fail-closed UI) |
| P0-5 | Keep watching **hacs/default#9004** — no code action; do not spam maintainers | Queue is months-scale; custom-repo remains the path | open |
## P1 — maintainability (next engineering focus)
| ID | Action | Why | Status |
|---|---|---|---|
| P1-1 | **Extract** from `houseplan-card.ts`: Plan editor, Devices editor, Decor/backdrop tools, dialogs → separate modules/components; keep pure math where it is | 8691 LOC god-object is the #1 engineering risk | open |
| P1-2 | Enable **`noImplicitAny`** in stages (devices → logic → card shell); introduce a thin typed `Hass` facade | `strict` is currently nominal | open |
| P1-3 | Split **`styles.ts`** by surface (view / editors / dialogs / kiosk) | 2223 LOC CSS sink | open |
| P1-4 | Unit-test **orchestration hotspots**: `_clickDevice` policy wiring, config write-chain conflict, mode enter/exit | Smokes-only today | open |
| P1-5 | Add **`expected_rev` (or per-key CAS)** to `layout/update` *or* document “single active editor” as the contract in ARCHITECTURE | Multi-tablet drag races | open |
## P2 — product depth inside SCOPE
| ID | Action | Why | Status |
|---|---|---|---|
| P2-1 | **Registry-driven room suggestions** after floors-import (bind suggested polygons/areas) | SCOPE J4; PRODUCT “next move”; moat vs card-only peers | open |
| P2-2 | Plan-level **security glance** badge (all locked / N open) | SCOPE known gap; kiosk value | open |
| P2-3 | Touch ergonomics pass on Plan/Devices editors | Admin-on-tablet persona | open |
| P2-4 | Options-flow richness: expose exclude domains / LQI thresholds / clearer `admin_only` | ROADMAP Phase 8 | open |
| P2-5 | Plan upload **auto-downscale** / max-dimension guidance | ROADMAP Phase 9; prevents huge SVG pain | open |
| P2-6 | Replace remaining **real-house README screenshots** with synthetic | STATUS privacy watchlist | open |
## P3 — quality-scale & hygiene
| ID | Action | Why | Status |
|---|---|---|---|
| P3-1 | Measure backend coverage; drive toward **≥95%** or revise the goal honestly | quality_scale todo | open |
| P3-2 | **mypy strict** (or staged) | Platinum todo | open |
| P3-3 | `docs-troubleshooting` + `docs-examples` (Gold) | quality_scale todo | open |
| P3-4 | Tighten MARKER_SCHEMA (`binding` pattern, hex `ripple_color`, positive decor sizes, space id regex) | Validation gaps | **done 2026-08-05** |
| P3-5 | Fix quality_scale filename (`test_ha_config_flow.py`); remove or re-wire dead card `tap_action` | Nits that confuse agents | **done 2026-08-05** (filename fixed; tap_action documented deprecated) |
| P3-6 | Refresh stale doc headers: ARCHITECTURE tree, UX-MODES “no code yet”, PRODUCT competitor table | Truth decay | open |
| P3-7 | Investigate / soften `smoke_opening_measure` magnet `1e-6` placement checks | Known env-sensitive red | open |
| P3-8 | Exception / icon translations | ROADMAP Phase 8 | open |
| P3-9 | Resource cleanup on integration removal + YAML-mode fallback doc | ROADMAP Phase 8 | open |
## Explicit do-not-do (reaffirmed)
Do **not** prioritize these even if competitors ship them:
1. Furniture / wall CAD (easy-floorplan’s game).
2. 3D / glb viewers.
3. Vacuum clean/zone **commands** (display-only stays).
4. Cloud sync / accounts.
5. Music-note / directional TV ripple polish from issue #3 backlog.
6. History rewrite to purge old house assets from git (breaks HACS tags).
## Suggested sequencing for an agent sprint
```
Week theme A — Trust & story
P0-3 README diff → P0-1 demo video → P0-2 forum/Reddit
P0-4 write-policy alignment (small code + options default)
Week theme B — Carve the god-object
P1-1 extract one editor (Devices is smallest) + P1-4 tests for the seam
P1-2 noImplicitAny on the extracted module only
Week theme C — Moat feature
P2-1 registry room suggestions (design in ARCHITECTURE first)
```
Avoid mixing A+B+C in one PR. Distribution P0s do not require waiting on P1.
## Success metrics (lightweight)
| Metric | Now (2026-08-05) | Near-term target |
|---|---|---|
| GitHub ★ | 21 | 100+ after GIF + posts + HACS visibility |
| HACS default | #9004 open | merged (external) |
| `houseplan-card.ts` LOC | ~8691 | <5000 after first extract wave |
| Backend coverage | unmeasured | number published in quality_scale comment |
| Open P0 items | 5 | 0 |
## Pointers
- Market context: [`AUDIT-MARKET.md`](AUDIT-MARKET.md)
- Quality detail: [`AUDIT-QUALITY.md`](AUDIT-QUALITY.md)
- Gaps / matrix: [`AUDIT-FUNCTIONAL.md`](AUDIT-FUNCTIONAL.md)
- Guard rail: [`SCOPE.md`](../../../docs/SCOPE.md)
- Living ops: [`STATUS.md`](../../../docs/STATUS.md)
-80
View File
@@ -1,80 +0,0 @@
# Project audit — index
> **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
> version audited: **v1.58.0**.
>
> **Policy at the snapshot date:** this pack supplemented `PRODUCT-2026-07-05.md`,
> `STATUS.md`, `SCOPE.md`, `ARCHITECTURE.md`, `ROADMAP.md`. When they disagree
> on *current* market numbers, prefer this pack until `PRODUCT.md` is refreshed.
## Pack contents
| File | What it answers |
|---|---|
| [`AUDIT-MARKET.md`](AUDIT-MARKET.md) | Potential, demand shape, competitors, positioning, risks |
| [`AUDIT-QUALITY.md`](AUDIT-QUALITY.md) | Implementation quality, architecture, security, tests, tech debt |
| [`AUDIT-FUNCTIONAL.md`](AUDIT-FUNCTIONAL.md) | Feature integrity, systematicity, claim↔code parity, gaps |
| [`AUDIT-RECOMMENDATIONS.md`](AUDIT-RECOMMENDATIONS.md) | Prioritized actions (P0–P3) with rationale |
## Executive verdict (one screen)
**House Plan is a high-craft, scope-disciplined product in a niche that suddenly
got a fast-growing peer.** Engineering quality (validation, tap security, CI
layers, quality-scale honesty, docs discipline) is well above typical HACS
cards. The dominant risks are no longer “can we build it?” — they are
**discoverability**, **maintainability of a 8.7k-LOC Lit god-object**, and
**losing the GUI-floorplan narrative to easy-floorplan** (≈430★ vs our ≈21★
as of 2026-08-05; a month earlier PRODUCT.md listed easy-floorplan at 11★).
| Dimension | Grade | One-line |
|---|---|---|
| Product mission / scope discipline | **A** | SCOPE.md is unusually sharp; non-goals held |
| Feature depth vs mission | **A−** | Jobs J1–J7 closed; a few polish gaps |
| Implementation quality (backend) | **A−** | Strong WS/HTTP/auth/file races; coverage % still todo |
| Implementation quality (frontend) | **B** | Pure modules good; card shell is a maintainability bomb |
| Test strategy | **A−** | 4 layers + smoke policy; human matrix stale |
| Competitive moat (technical) | **A−** | Server-side storage + area-bound rooms still unique |
| Competitive position (market) | **C+** | Traction lagging the wave; HACS default still queued |
| Distribution / social proof | **C** | Demo stand exists; forum/Reddit/GIF still open |
| Bus factor / docs | **B+** | Excellent docs; single maintainer |
**Do not** expand into 3D, furniture CAD, vacuum commands, or cloud — SCOPE
forbids them and competitors already own parts of that surface. **Do** close
the distribution gap and keep the moat (registry depth, multi-client storage,
overlays that feel like a home — not a drawing app).
## How agents should use this
1. Before a feature: check `SCOPE.md` → then `AUDIT-FUNCTIONAL.md` gaps → then
`AUDIT-RECOMMENDATIONS.md` priority.
2. Before a refactor: read `AUDIT-QUALITY.md` top-10 debt; prefer extracting
from `houseplan-card.ts`, not rewriting working pure modules.
3. Before go-to-market or README claims: read `AUDIT-MARKET.md` — star counts
and competitor maturity change monthly; re-verify with GitHub API.
4. After acting on a P0/P1 item: update the relevant AUDIT file’s “Status”
line in the same commit (short note + date), and bump STATUS.md watchlist
if the project-level state changed.
## Method
- Read: PRODUCT, STATUS, ARCHITECTURE, ROADMAP, SCOPE, UX-MODES, TESTING,
CONTRIBUTING, quality_scale.yaml, manifest, key `src/` + `custom_components/`
modules.
- Metrics: `wc -l`, bundle size, `npm`/`pytest` inventory, GitHub API star
counts (2026-08-05), hacs/default#9004 state.
- Code-quality pass: god-object sizing, auth UI↔API, tap-action model,
validation parity, test layer map.
- Explicitly **not** a full security penetration test or coverage measurement.
## Related living docs
- Market rationale (older): [`PRODUCT-2026-07-05.md`](../PRODUCT-2026-07-05.md).
- Guard rail: `SCOPE.md`
- Current ops snapshot: `STATUS.md`
- Design: `ARCHITECTURE.md`, `CANVAS.md`, `BACKDROP.md`, `VACUUM.md`, `SUN.md`
@@ -1,39 +0,0 @@
# Open spans + wall-centric Delete — Implementation Plan
> **For agentic workers:** execute task-by-task; checkboxes track progress.
**Goal:** Partial virtual wall stretches (`space.open_spans`) + Delete operates on walls (close → merge/delete room), then ship `v1.59.0-beta.6`.
**Architecture:** Pure helpers in `src/open-spans.ts`; card wires two-click openwall and wall-centric delete; `open_to` remains the light-zone index derived from spans; legacy `open_to`-only configs expand to full sharedBoundary on read.
**Tech Stack:** TypeScript, Lit card, node:test, Playwright smokes, GitHub prerelease.
**Spec:** [`docs/superpowers/specs/2026-08-05-open-spans-delete-design.md`](../../../docs/superpowers/specs/2026-08-05-open-spans-delete-design.md)
## Global Constraints
- Cursor openwall = crosshair (not pointer)
- Openings forbidden on virtual; purge on open
- Thickness clear on open; restore neighbour / DRAW_WALL_DEFAULT_CM on close
- Delete: virtual→close; shared→confirm merge whole pair; outer/inside→confirm delete room
- Version bump to `1.59.0-beta.6` for this ship
## File map
| File | Role |
|------|------|
| `src/open-spans.ts` | CRUD, legacy expand, snap/clamp, thickness+opening side effects, rekey/degrade |
| `src/houseplan-card.ts` | Gestures, render cuts from spans, merge via existing dialog |
| `src/styles.ts` | openwall crosshair |
| `src/i18n/en.json`, `ru.json` | Toasts / titles |
| `test/open-spans.test.mjs` | Unit |
| `demo/smoke_openwall.mjs` (+ hover/delete as needed) | Browser |
| docs CHANGELOG/STATUS/ARCHITECTURE | Ship notes |
## Tasks
- [ ] Task 1: `open-spans.ts` + unit tests
- [ ] Task 2: Wire `_openPairs` / openwall 2-click / openings refuse+purge
- [ ] Task 3: Wall-centric Delete + merge confirm path
- [ ] Task 4: Smokes, i18n, docs
- [ ] Task 5: Bump beta.6, build, commit, tag, GitHub prerelease
-245
View File
@@ -1,245 +0,0 @@
/**
* #266: one-shot mechanical splitter. Reads src/styles.ts, cuts the css``
* template into top-level blocks (rule, @media, @keyframes — with their
* leading comments), classifies every block by the owner of its first
* selector token, and writes five surface files plus the aggregator.
* A token missing from the table aborts the run: no silent "misc" bucket.
*/
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
const ZONES = { base: [], plan: [], devices: [], chrome: [], dialogs: [] };
const T = {};
const assign = (zone, tokens) => { for (const t of tokens.split(/\s+/)) T[t] = zone; };
assign('base', ':host .sr-only ha-card .bootveil .toast .empty .spacer');
assign('plan', `.stage .plan-svg .hp-paper .hp-static-stage .hp-day-cycle-bg .hp-day-cycle-sun .hp-day-cycle-env
.seg .vertex .pathline .preview .active-axis .active-vertex .physical-hit .physical-chrome .physical-drag
.drawwall .drawwall-preview .drawwall-preview-fill .zero-wall .wall-repair-preview
.wallbody .wallbody-fill .wallthick-hover .griddot
.plan-snap-node .plan-snap-line .plan-snap-overlay .hidden-wall-line .hidden-wall-node .hidden-wall-diagnostic
.room .room-outline .room-hover-fill .room-hover-halo .room-hover-outline .roomlabel
.rlhandle .rlgear .rlgearbtn .rlname .rlgo
.measurelabel .measurelayer .compass .homearrow .zoomwrap .zoomctl .zoombadge
.decorlayer .glow-spot .glow-pools .glow-pools-frame .glow-base-layer .glow-base-tunnels .sunlayer
.iso-opening-panel .iso-ambient-shadow .iso-wall-side .iso-wall-top
.iso-floor-side .iso-side-hi .iso-side-lo .iso-top-hi .iso-top-lo .iso-shadows-svg .iso-underlay-svg
.iso-walls-svg .iso-underlay .iso-shadows .iso-walls .iso-openings
.opening-preview .opening-preview-dot .opening-dimension .opening-dimensions .opening-dimension-line
.opening-dimension-tick .op-leaf .op-arc .op-hit .op-outline .passage-preview-boundary .passage-preview-cut
.aligndot .alignline .alignmsg .rszhandle .rszicon .rszleader .rszhalo .rszink .rszmeasurehalo
.rszmeasureink .rszmeasurelayer .bdframe .dtframe .dtarea .devlayer .fixedfloor-loading .fixedfloor-error
line .projection-toggle`);
assign('devices', `.dev .device-pulse .device-shell .device-shell-frame .device-core .device-sections
.devicepreview-empty .activity-dot .vactrail .vacpuck .temprange .kdot .kioskdots`);
assign('chrome', `.editbar .editbar-end .editbar-tools .tab .tabs .modetab .modes .decorbar
.editorchrome .editorchrome-inner .menu .menuwrap .dropbtn .droppanel .hdr .farhint .rhint .tip .togglehint`);
assign('dialogs', `hp-dialog .btn .oplock .oplock-core .oplock-shell .rrow .colorrow .ripple-colorrow
.ripple-sizerow .optimize-details .optimize-live .optimize-cleanup .optimize-selected .recoveryoverlay
.savedplan .savedplans .savedmeta .backupdetails .backupactions .backupfile .backupcontent .backupplanonly
.backupplanonlystatus .backupupload .backupchoices .backupconfirm .backuperror .backupsummary .backupwarn
.backupcounts .backupbody .pdf .pdfedit .pdflist .pdftag .planprev .planrow .planname .cardpreview
.namein .descin .areasel .entrow .entlist .entbtn .cand .candlist .srcrow .gsrow .inforow .infodesc
.dispsection .floorrow .fileupload .filebtn .furnsize .furnhd .furnitem .furnbody .furngroup .furnhint
.furnpalette .furnprev .furnrow .wallthick-dlg .vacpicker .vacsource .vacsource-list .vacsource-meta
.vacsource-warning .vacdiag .vacfit .vacfitdot .vacfithandle .vacfitknob .vacbox .vaccalbar .vacxcme
.sunrow .suncol .aboutlink .aboutver .count .head .title .markerhelplabel .markerhelpfield .markerradios
.markeractions .markersaveactions .markerlightgroup .markerlightdisabled .markerglowvalue
.markerbadgetechnical .opening-entity-candidate .opening-entity-empty .habindingbanner .bindsel
.bindharow .curbind .ctrlchip .ctrlchips .ctrlopt .ctrlstate .ctrlstates .ctrllist .oprow .iconauto
.rtest .rtesticon ha-icon-picker`);
const argValue = (name, dflt) => {
const i = process.argv.indexOf(`--${name}`);
return i === -1 ? dflt : process.argv[i + 1];
};
const sourcePath = argValue('source', 'src/styles.ts');
// Cumulative slice list in the FINAL aggregator order suffix (#266 §1.4):
// e.g. --take chrome,dialogs keeps base+plan+devices inline and appends the
// two extracted files after the inline remainder.
const TAKE = (argValue('take', 'base,plan,devices,chrome,dialogs') || '')
.split(',').map((z) => z.trim()).filter(Boolean);
const FINAL_ORDER = ['base', 'plan', 'devices', 'chrome', 'dialogs'];
for (const zone of TAKE) if (!FINAL_ORDER.includes(zone)) throw new Error(`unknown zone ${zone}`);
const source = readFileSync(sourcePath, 'utf8');
const m = source.match(/css`([\s\S]*)`;\s*$/);
if (!m) throw new Error('styles.ts: css template not found');
const cssBody = m[1];
// Cut into top-level blocks with leading comments/blank lines attached.
const blocks = [];
let i = 0;
let pending = '';
while (i < cssBody.length) {
const rest = cssBody.slice(i);
const cm = rest.match(/^\s*\/\*[\s\S]*?\*\//);
if (cm && !rest.slice(0, rest.indexOf('/*')).includes('{')) {
// comment before any brace: attach to the next block
pending += cm[0];
i += cm[0].length;
continue;
}
const open = cssBody.indexOf('{', i);
if (open === -1) { pending += cssBody.slice(i); break; }
const header = cssBody.slice(i, open);
let depth = 1, j = open + 1;
while (j < cssBody.length && depth > 0) {
if (cssBody[j] === '{') depth++;
else if (cssBody[j] === '}') depth--;
j++;
}
blocks.push({ text: pending + header + cssBody.slice(open, j), header: header.trim() });
pending = '';
i = j;
}
const zoneOfSelector = (header) => {
// A leading :host(...) compound GATES a rule, it does not own it: the owner
// is the first meaningful token after the gate. Classifying such groups
// into base moved them AHEAD of their surface in the cascade and flipped
// equal-specificity winners (smoke_device_icon_design caught alert shells
// and dark-unavailable cores) — the owner surface keeps their position.
const owner = (sel) => {
let rest = sel.trim().replace(/^:host(\([^)]*\))?\s*/, '');
if (!rest) return ':host';
const t = rest.match(/^[.:#\[]?[\w-]+/);
return t ? t[0] : null;
};
const zones = new Set();
for (const sel of header.split(',')) {
const token = owner(sel);
if (!token) continue;
const zone = T[token];
if (!zone) throw new Error(`unclassified token «${token}» in «${header.slice(0, 80)}»`);
zones.add(zone);
}
if (!zones.size) throw new Error(`no tokens in «${header.slice(0, 80)}»`);
return zones.size === 1 ? [...zones][0] : 'base';
};
const innerHeader = (header) => header.replace(/^@(media|supports)[^{]*$/s, '').trim();
for (const block of blocks) {
let zone;
if (/^@keyframes/.test(block.header)) {
// keyframes are owned by their consumers; classify by name prefix
const name = block.header.replace(/^@keyframes\s+/, '');
zone = /^(hp-dev|dev|pulse|vac)/.test(name) ? 'devices'
: /^(hp-spin|spin|fade|toast)/.test(name) ? 'base' : 'base';
} else if (/^@(media|supports)/.test(block.header)) {
// Classify by ALL inner rules. A single-zone wrapper moves whole; a mixed
// one is split into consecutive per-zone copies of the wrapper so every
// rule stays with its owner surface without crossing zone order.
const openAt = block.text.indexOf('{');
const inner = block.text.slice(openAt + 1, block.text.lastIndexOf('}'));
const innerBlocks = [];
{
let k = 0, pend = '';
while (k < inner.length) {
const rest2 = inner.slice(k);
const cm2 = rest2.match(/^\s*\/\*[\s\S]*?\*\//);
if (cm2 && !rest2.slice(0, rest2.indexOf('/*')).includes('{')) {
pend += cm2[0]; k += cm2[0].length; continue;
}
const o2 = inner.indexOf('{', k);
if (o2 === -1) break;
const h2 = inner.slice(k, o2);
let d2 = 1, j2 = o2 + 1;
while (j2 < inner.length && d2 > 0) {
if (inner[j2] === '{') d2++;
else if (inner[j2] === '}') d2--;
j2++;
}
innerBlocks.push({ text: pend + h2 + inner.slice(o2, j2), header: h2.trim() });
pend = '';
k = j2;
}
}
const innerZones = innerBlocks.map((b) => zoneOfSelector(b.header));
const uniq = [...new Set(innerZones)];
if (uniq.length <= 1) {
zone = uniq[0] ?? 'base';
} else {
const wrapperHeader = block.text.slice(0, openAt).trimEnd();
let g = 0;
while (g < innerBlocks.length) {
const zg = innerZones[g];
let h = g;
while (h < innerBlocks.length && innerZones[h] === zg) h++;
const bodyPart = innerBlocks.slice(g, h).map((b) => b.text).join('');
const copy = `${wrapperHeader} {${bodyPart}\n }`;
ZONES[zg].push(copy);
g = h;
}
// mark handled: skip the shared push below
block.zone = 'SPLIT';
continue;
}
} else {
zone = zoneOfSelector(block.header);
}
ZONES[zone].push(block.text);
block.zone = zone;
}
mkdirSync('src/styles', { recursive: true });
const taken = FINAL_ORDER.filter((zone) => TAKE.includes(zone));
const kept = FINAL_ORDER.filter((zone) => !TAKE.includes(zone));
const NAMES = { base: 'base', plan: 'plan', devices: 'devices', chrome: 'chrome', dialogs: 'dialogs' };
const DOC = {
base: 'Host, variables, resets and cross-surface rules',
plan: 'The plan scene: stage, walls, axes, snap, decor, iso and resize ink',
devices: 'Device markers, shells, pulses and vacuum presentation',
chrome: 'Editor chrome: toolbars, tabs, menus and hints',
dialogs: 'Dialogs, forms, buttons and pickers',
};
const tidy = (chunk) => chunk.replace(/^\n+/, '').replace(/\n+$/, '');
for (const zone of taken) {
const body = ZONES[zone].map(tidy).join('\n');
writeFileSync(`src/styles/${NAMES[zone]}.styles.ts`,
`/** ${DOC[zone]} (#266, split from styles.ts). */\nimport { css } from 'lit';\n\nexport const ${zone}Styles = css\`\n${body}\n\`;\n`);
console.log(zone, ZONES[zone].length, 'blocks ->', `src/styles/${NAMES[zone]}.styles.ts`);
}
if (kept.length) {
// Intermediate slice: the not-yet-extracted zones stay inline IN SOURCE
// ORDER, extracted zones append after them in the final-order suffix, so
// every relative position matches the final aggregator and golden can gate
// each slice.
const keptBlocks = blocks.filter((block) => !taken.includes(block.zone));
const imports = taken.map((zone) =>
`import { ${zone}Styles } from './styles/${NAMES[zone]}.styles';`).join('\n');
writeFileSync('src/styles.ts', `/** Styles of the House Plan card — being split by surface (#266). */
import { css } from 'lit';
import type { CSSResultGroup } from 'lit';
${imports}
export { ${taken.map((zone) => `${zone}Styles`).join(', ')} };
const inlineStyles = css\`
${keptBlocks.map((block) => tidy(block.text)).join('\n')}
\`;
export const cardStyles: CSSResultGroup = [
inlineStyles, ${taken.map((zone) => `${zone}Styles`).join(', ')},
];
`);
console.log('aggregator with inline remainder written:', kept.join('+'), 'inline;', taken.join(', '), 'extracted');
} else writeFileSync('src/styles.ts', `/**
* Styles of the House Plan card — assembled from the surface files (#266).
*
* The ORDER of this array is part of the cascade contract: rules of equal
* specificity resolve by position, and the golden set was accepted against
* exactly this order. Do not reorder without re-reviewing the golden set.
*/
import type { CSSResultGroup } from 'lit';
import { baseStyles } from './styles/base.styles';
import { planStyles } from './styles/plan.styles';
import { devicesStyles } from './styles/devices.styles';
import { chromeStyles } from './styles/chrome.styles';
import { dialogsStyles } from './styles/dialogs.styles';
export { baseStyles, planStyles, devicesStyles, chromeStyles, dialogsStyles };
export const cardStyles: CSSResultGroup = [
baseStyles, planStyles, devicesStyles, chromeStyles, dialogsStyles,
];
`);
console.log('aggregator written');
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Partial open (virtual) wall spans — docs/superpowers/specs/2026-08-05-open-spans-delete-design.md
* Partial open (virtual) wall spans — legacy/docs/superpowers/specs/2026-08-05-open-spans-delete-design.md
*
* Stored on the space as `open_spans: [{ a, b }]` in normalised 0..1 coords.
* `rooms[].open_to` remains the light-zone connectivity index derived from spans.