diff --git a/AGENTS.md b/AGENTS.md index 930f7a66..17fc8740 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/custom_components/houseplan/validation.py b/custom_components/houseplan/validation.py index cf207533..dad2db2c 100644 --- a/custom_components/houseplan/validation.py +++ b/custom_components/houseplan/validation.py @@ -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 diff --git a/demo/README.md b/demo/README.md deleted file mode 100644 index 5532766b..00000000 --- a/demo/README.md +++ /dev/null @@ -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: ``/`` 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= 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). diff --git a/demo/benchmark_coordinate_write_barrier.mjs b/demo/benchmark_coordinate_write_barrier.mjs deleted file mode 100644 index ba545dfe..00000000 --- a/demo/benchmark_coordinate_write_barrier.mjs +++ /dev/null @@ -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; diff --git a/demo/capture_wall_strip_backup.mjs b/demo/capture_wall_strip_backup.mjs deleted file mode 100644 index d0d9d8a0..00000000 --- a/demo/capture_wall_strip_backup.mjs +++ /dev/null @@ -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 ' - + '[--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 })); diff --git a/demo/shot_cover_move.mjs b/demo/shot_cover_move.mjs deleted file mode 100644 index b27eedda..00000000 --- a/demo/shot_cover_move.mjs +++ /dev/null @@ -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'); diff --git a/demo/shot_cover_states.mjs b/demo/shot_cover_states.mjs deleted file mode 100644 index abfe3e2a..00000000 --- a/demo/shot_cover_states.mjs +++ /dev/null @@ -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'); diff --git a/demo/shot_daynight.mjs b/demo/shot_daynight.mjs deleted file mode 100644 index 461e1ab6..00000000 --- a/demo/shot_daynight.mjs +++ /dev/null @@ -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'); diff --git a/demo/shot_furniture.mjs b/demo/shot_furniture.mjs deleted file mode 100644 index 7be8aa39..00000000 --- a/demo/shot_furniture.mjs +++ /dev/null @@ -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); diff --git a/demo/shot_motion_sense.mjs b/demo/shot_motion_sense.mjs deleted file mode 100644 index 89768e68..00000000 --- a/demo/shot_motion_sense.mjs +++ /dev/null @@ -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'); diff --git a/demo/shot_opening_place.mjs b/demo/shot_opening_place.mjs deleted file mode 100644 index ba432886..00000000 --- a/demo/shot_opening_place.mjs +++ /dev/null @@ -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'); diff --git a/demo/smoke_household_journeys.mjs b/demo/smoke_household_journeys.mjs index f625892e..99863739 100644 --- a/demo/smoke_household_journeys.mjs +++ b/demo/smoke_household_journeys.mjs @@ -11,7 +11,7 @@ // телефона (390 px) — та самая, на которой ломается первый контакт. // // Граница честности: это синтетический стенд. Реальная установка владельца в -// этом заходе читалась только на чтение (профиль в docs/QUALITY-560.md), +// этом заходе читалась только на чтение (профиль в legacy/docs/QUALITY-560.md), // добровольцев и физического Companion в проверке не было. import { launch, check, finish, watchPage } from './serve.mjs'; diff --git a/docs/FILTERING.md b/docs/FILTERING.md index 2d87af6b..e2bb902d 100644 --- a/docs/FILTERING.md +++ b/docs/FILTERING.md @@ -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 diff --git a/docs/README.ru.md b/docs/README.ru.md deleted file mode 100644 index 274d095b..00000000 --- a/docs/README.ru.md +++ /dev/null @@ -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). diff --git a/docs/SCOPE.md b/docs/SCOPE.md index 0e72e08e..3089319c 100755 --- a/docs/SCOPE.md +++ b/docs/SCOPE.md @@ -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 diff --git a/docs/STATUS.md b/docs/STATUS.md index 03460a76..ed133b17 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -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 diff --git a/docs/specs/006-vacuum-xcme-path.md b/docs/specs/006-vacuum-xcme-path.md index 249b76db..a8c68843 100644 --- a/docs/specs/006-vacuum-xcme-path.md +++ b/docs/specs/006-vacuum-xcme-path.md @@ -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 ## Цель diff --git a/docs/specs/007-vacuum-valetudo-room-outlines.md b/docs/specs/007-vacuum-valetudo-room-outlines.md index 1657ecdb..3f8b7b1d 100644 --- a/docs/specs/007-vacuum-valetudo-room-outlines.md +++ b/docs/specs/007-vacuum-valetudo-room-outlines.md @@ -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 ## Цель diff --git a/docs/specs/058-vacuum-stage1.md b/docs/specs/058-vacuum-stage1.md index 04f51299..9211f139 100644 --- a/docs/specs/058-vacuum-stage1.md +++ b/docs/specs/058-vacuum-stage1.md @@ -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` ## Назначение документа diff --git a/docs/testing-notes/core-checklist.md b/docs/testing-notes/core-checklist.md index baae7741..2cbf3100 100644 --- a/docs/testing-notes/core-checklist.md +++ b/docs/testing-notes/core-checklist.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 diff --git a/docs/testing-notes/devices.md b/docs/testing-notes/devices.md index 10d566f6..e15d2c76 100644 --- a/docs/testing-notes/devices.md +++ b/docs/testing-notes/devices.md @@ -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 diff --git a/legacy/README.md b/legacy/README.md index 1da93f70..bd7a11d5 100644 --- a/legacy/README.md +++ b/legacy/README.md @@ -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). diff --git a/legacy/demo/dbg_click.mjs b/legacy/demo/dbg_click.mjs deleted file mode 100644 index ca64ff93..00000000 --- a/legacy/demo/dbg_click.mjs +++ /dev/null @@ -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(); diff --git a/legacy/demo/repro_issue3.mjs b/legacy/demo/repro_issue3.mjs deleted file mode 100644 index cef640b1..00000000 --- a/legacy/demo/repro_issue3.mjs +++ /dev/null @@ -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(); diff --git a/legacy/docs/089-isometric-view-draft.md b/legacy/docs/089-isometric-view-draft.md deleted file mode 100644 index 8169fdd6..00000000 --- a/legacy/docs/089-isometric-view-draft.md +++ /dev/null @@ -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-карточки. diff --git a/legacy/docs/PRODUCT-2026-07-05.md b/legacy/docs/PRODUCT-2026-07-05.md deleted file mode 100644 index ecd2481c..00000000 --- a/legacy/docs/PRODUCT-2026-07-05.md +++ /dev/null @@ -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. diff --git a/legacy/docs/PRODUCT-IMPROVEMENT-PLAN.ru.md b/legacy/docs/PRODUCT-IMPROVEMENT-PLAN.ru.md deleted file mode 100644 index 77288083..00000000 --- a/legacy/docs/PRODUCT-IMPROVEMENT-PLAN.ru.md +++ /dev/null @@ -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, безопасное быстрое действие, создание/поддержку плана или качество реализации этих сценариев. diff --git a/docs/QUALITY-560.md b/legacy/docs/QUALITY-560.md similarity index 100% rename from docs/QUALITY-560.md rename to legacy/docs/QUALITY-560.md diff --git a/docs/ROADMAP.md b/legacy/docs/ROADMAP.md similarity index 98% rename from docs/ROADMAP.md rename to legacy/docs/ROADMAP.md index 845a565e..7fdce895 100644 --- a/docs/ROADMAP.md +++ b/legacy/docs/ROADMAP.md @@ -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. diff --git a/docs/STATUS-FEATURES.md b/legacy/docs/STATUS-FEATURES.md similarity index 100% rename from docs/STATUS-FEATURES.md rename to legacy/docs/STATUS-FEATURES.md diff --git a/legacy/docs/audit-v1.58.0/AUDIT-FUNCTIONAL.md b/legacy/docs/audit-v1.58.0/AUDIT-FUNCTIONAL.md deleted file mode 100644 index ae834e47..00000000 --- a/legacy/docs/audit-v1.58.0/AUDIT-FUNCTIONAL.md +++ /dev/null @@ -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. diff --git a/legacy/docs/audit-v1.58.0/AUDIT-MARKET.md b/legacy/docs/audit-v1.58.0/AUDIT-MARKET.md deleted file mode 100644 index 7e17fe45..00000000 --- a/legacy/docs/audit-v1.58.0/AUDIT-MARKET.md +++ /dev/null @@ -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. diff --git a/legacy/docs/audit-v1.58.0/AUDIT-QUALITY.md b/legacy/docs/audit-v1.58.0/AUDIT-QUALITY.md deleted file mode 100644 index 263383da..00000000 --- a/legacy/docs/audit-v1.58.0/AUDIT-QUALITY.md +++ /dev/null @@ -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 ``, 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. diff --git a/legacy/docs/audit-v1.58.0/AUDIT-RECOMMENDATIONS.md b/legacy/docs/audit-v1.58.0/AUDIT-RECOMMENDATIONS.md deleted file mode 100644 index 8144bbc1..00000000 --- a/legacy/docs/audit-v1.58.0/AUDIT-RECOMMENDATIONS.md +++ /dev/null @@ -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) diff --git a/legacy/docs/audit-v1.58.0/AUDIT.md b/legacy/docs/audit-v1.58.0/AUDIT.md deleted file mode 100644 index f08d25ed..00000000 --- a/legacy/docs/audit-v1.58.0/AUDIT.md +++ /dev/null @@ -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` diff --git a/legacy/docs/implementation-plans/2026-08-05-open-spans-delete.md b/legacy/docs/implementation-plans/2026-08-05-open-spans-delete.md deleted file mode 100644 index c6dfec24..00000000 --- a/legacy/docs/implementation-plans/2026-08-05-open-spans-delete.md +++ /dev/null @@ -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 diff --git a/docs/superpowers/specs/2026-08-05-device-visual-state-design.md b/legacy/docs/superpowers/specs/2026-08-05-device-visual-state-design.md similarity index 100% rename from docs/superpowers/specs/2026-08-05-device-visual-state-design.md rename to legacy/docs/superpowers/specs/2026-08-05-device-visual-state-design.md diff --git a/docs/superpowers/specs/2026-08-05-free-wall-paths-design.md b/legacy/docs/superpowers/specs/2026-08-05-free-wall-paths-design.md similarity index 100% rename from docs/superpowers/specs/2026-08-05-free-wall-paths-design.md rename to legacy/docs/superpowers/specs/2026-08-05-free-wall-paths-design.md diff --git a/docs/superpowers/specs/2026-08-05-open-spans-delete-design.md b/legacy/docs/superpowers/specs/2026-08-05-open-spans-delete-design.md similarity index 100% rename from docs/superpowers/specs/2026-08-05-open-spans-delete-design.md rename to legacy/docs/superpowers/specs/2026-08-05-open-spans-delete-design.md diff --git a/docs/superpowers/specs/2026-08-08-always-static-device-icon-design.md b/legacy/docs/superpowers/specs/2026-08-08-always-static-device-icon-design.md similarity index 100% rename from docs/superpowers/specs/2026-08-08-always-static-device-icon-design.md rename to legacy/docs/superpowers/specs/2026-08-08-always-static-device-icon-design.md diff --git a/docs/superpowers/specs/2026-08-08-device-display-preview-design.md b/legacy/docs/superpowers/specs/2026-08-08-device-display-preview-design.md similarity index 100% rename from docs/superpowers/specs/2026-08-08-device-display-preview-design.md rename to legacy/docs/superpowers/specs/2026-08-08-device-display-preview-design.md diff --git a/docs/superpowers/specs/2026-08-08-editor-context-overlay-design.md b/legacy/docs/superpowers/specs/2026-08-08-editor-context-overlay-design.md similarity index 100% rename from docs/superpowers/specs/2026-08-08-editor-context-overlay-design.md rename to legacy/docs/superpowers/specs/2026-08-08-editor-context-overlay-design.md diff --git a/docs/superpowers/specs/2026-08-08-ha-disabled-devices-design.md b/legacy/docs/superpowers/specs/2026-08-08-ha-disabled-devices-design.md similarity index 100% rename from docs/superpowers/specs/2026-08-08-ha-disabled-devices-design.md rename to legacy/docs/superpowers/specs/2026-08-08-ha-disabled-devices-design.md diff --git a/docs/superpowers/specs/2026-08-08-opening-tunnel-room-fill-design.md b/legacy/docs/superpowers/specs/2026-08-08-opening-tunnel-room-fill-design.md similarity index 100% rename from docs/superpowers/specs/2026-08-08-opening-tunnel-room-fill-design.md rename to legacy/docs/superpowers/specs/2026-08-08-opening-tunnel-room-fill-design.md diff --git a/docs/superpowers/specs/2026-08-08-unified-boundary-tool-design.md b/legacy/docs/superpowers/specs/2026-08-08-unified-boundary-tool-design.md similarity index 100% rename from docs/superpowers/specs/2026-08-08-unified-boundary-tool-design.md rename to legacy/docs/superpowers/specs/2026-08-08-unified-boundary-tool-design.md diff --git a/docs/superpowers/specs/2026-08-09-vacuum-integration-coverage-design.md b/legacy/docs/superpowers/specs/2026-08-09-vacuum-integration-coverage-design.md similarity index 100% rename from docs/superpowers/specs/2026-08-09-vacuum-integration-coverage-design.md rename to legacy/docs/superpowers/specs/2026-08-09-vacuum-integration-coverage-design.md diff --git a/docs/superpowers/specs/2026-08-10-plan-visual-continuity-design.md b/legacy/docs/superpowers/specs/2026-08-10-plan-visual-continuity-design.md similarity index 100% rename from docs/superpowers/specs/2026-08-10-plan-visual-continuity-design.md rename to legacy/docs/superpowers/specs/2026-08-10-plan-visual-continuity-design.md diff --git a/scripts/dev/styles-split.mjs b/scripts/dev/styles-split.mjs deleted file mode 100644 index bffec04d..00000000 --- a/scripts/dev/styles-split.mjs +++ /dev/null @@ -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'); diff --git a/src/open-spans.ts b/src/open-spans.ts index 2a68fdb9..f4b1c335 100644 --- a/src/open-spans.ts +++ b/src/open-spans.ts @@ -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.