mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 12:18:51 +00:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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 }));
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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);
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
## Цель
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
## Назначение документа
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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).
|
||||
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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-карточки.
|
||||
@@ -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.
|
||||
@@ -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, безопасное быстрое действие, создание/поддержку плана или качество реализации этих сценариев.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user