From 8d9bf896e28f3ffa7e082b1ef999b3e054e00d0e Mon Sep 17 00:00:00 2001 From: Matysh Date: Mon, 6 Jul 2026 00:08:14 +0300 Subject: [PATCH] docs: STATUS.md (session-context snapshot + documentation policy), new ROADMAP (quality-scale phases 7-10, universality), PRODUCT.md (market assessment, competitors verified 2026-07-06), DEVELOPMENT.md environment refresh --- docs/DEVELOPMENT.md | 20 +++++-- docs/PRODUCT.md | 74 ++++++++++++++++++++++++ docs/ROADMAP.md | 135 +++++++++++++++++++++++--------------------- docs/STATUS.md | 66 ++++++++++++++++++++++ 4 files changed, 228 insertions(+), 67 deletions(-) create mode 100644 docs/PRODUCT.md create mode 100644 docs/STATUS.md diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index dc920ca..d4ff981 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -2,10 +2,22 @@ ## Environment (cowork sessions) -- The source of truth for the code is the **git repository** (lives in `/tmp/hpc` during a session, - restored from `houseplan-card.git.bundle`: `git clone houseplan-card.git.bundle hpc`). -- The user's folder `houseplan/houseplan-card/` is a mirror of the repo (rsync after every commit) - + an up-to-date `houseplan-card.git.bundle`. +- The source of truth is **GitHub `main`** (https://github.com/Matysh/houseplan-card). + In a sandbox session restore from it or from `houseplan-card.git.bundle` + (`git clone houseplan-card.git.bundle hpcN` into a **fresh** /tmp directory). +- The user's folder `houseplan/houseplan-card/` is a file mirror (synced after every commit) + + an up-to-date `houseplan-card.git.bundle`. The mount cannot delete files — stale + artifacts linger there; git is authoritative. +- `/tmp` persists between sessions, **but files created in previous sessions belong to + `nobody` and are unreadable** (this hit `/tmp/hpc`, `/tmp/ha_jb`, `/tmp/shots/srv`). + Always clone into a new directory and re-run `npm ci`; ask the user to re-upload `ha_jb`. +- Headless Chromium for smoke tests: `PLAYWRIGHT_BROWSERS_PATH=/tmp/pw npx playwright + install chromium-headless-shell`, then run with `LD_LIBRARY_PATH` pointing to the + extracted lib dirs (`libs/lib/x86_64-linux-gnu:libs/usr/lib/x86_64-linux-gnu:.../nss`). +- Restart HA over SSH with `nohup ha core restart >/dev/null 2>&1 ": {"hidden":bool,"icon":str,"name":str} }, - "virtual_devices": [ {"id","space","name","icon","x","y","note"?, "entity_id"?} ], - "settings": {"exclude_integrations": [...], "group_lights": bool, ...} } -``` -- WS API v2: `houseplan/config/get|set`, `houseplan/plan/upload` (plan file → - `/houseplan/` via the process executor, served through a static path), layout as today. -- The card: when a server config exists it uses it; the dacha bundle data becomes a - **fallback example** and is then removed (a migration script will push it into the Store). -- Coordinate units: normalized (0..1 of the plan) for new configs — independence from the - source resolution; the dacha migration will convert 1489×1053 → normalized. +Track progress in `custom_components/houseplan/quality_scale.yaml` (done/exempt + comment). -## Phase 2 — Markup editor in the card ← ✅ CORE DONE in v1.4.0 (remaining: plan upload from the UI, view_box editing, editing existing rooms) -- A "Setup" mode (separate from drag layout): drawing/resizing room rectangles - on top of the plan, binding to an area (ha-area-picker selector), viewBox (frame) editing. -- Later: polygonal rooms (SVG path), plan rotations. -- Plan upload from the UI (file upload → WS) + picking existing media. +- [ ] `runtime-data`: move `hass.data[DOMAIN]` (stores, entry, write lock) to typed + `entry.runtime_data` (`type HouseplanConfigEntry = ConfigEntry[HouseplanData]`). + Keep WS command registration in `async_setup` (commands must survive entry reloads). +- [ ] `config-entry-unloading` (Silver, do it now): real `async_unload_entry` — unregister + update listeners via `entry.async_on_unload`; document what stays (static paths cannot + be unregistered — exempt comment). +- [ ] `test-before-setup`: verify storage is loadable/writable in `async_setup_entry`, + raise `ConfigEntryNotReady` on failure. +- [ ] `unique-config-entry`: replace the manual `_async_current_entries()` check with + `single_config_entry: true` in manifest. +- [ ] `config-flow-test-coverage` + backend tests for websocket_api (pytest-homeassistant-custom-component: + conflict/rev, admin_only enforcement, layout point ops, upload limits). +- [ ] Storage versioning: `Store(minor_version=...)` + `_async_migrate_func` skeleton **now**, + before the config schema evolves further (Bronze-adjacent, cheap while schema is stable). +- [ ] `quality_scale.yaml` with honest exempt list (no entities ⇒ entity rules N/A, no polling, no deps). -## Phase 3 — Device management ← ✅ DONE in v1.6.x (hiding, icon, name, model, link, description, PDF, rebinding, "+", virtual) -- A device panel in setup mode: a list of unplaced devices (with filters), drag from the panel - onto the plan; auto-layout "grid over the room" by button. -- Per-device overrides: hide, custom icon (ha-icon-picker), custom name. Stored in config. -- Configurable curation: integration/domain exclusions in the options flow instead of hardcode. +## Phase 8 — Quality Scale: Silver + selected Gold -## Phase 4 — Virtual devices ← ✅ basics in v1.6.x (CRUD of virtual markers in the shared dialog; further note/icon polish as needed) -- CRUD of virtual markers (name, icon, coordinates, note; optionally a link to an - entity/URL): septic tank, valve, a meter without a sensor, etc. Rendered like normal icons, - click → a card with the note or more-info of the bound entity. +- [ ] `test-coverage` ≥95% backend; frontend: extract remaining pure logic (view math, + marker resolution) into `logic.ts`/`devices.ts` and cover with node:test. +- [ ] `diagnostics.py`: config + layout dump with `async_redact_data` (redact names/links/PDF paths). +- [ ] `reconfiguration-flow` + richer **options flow**: admin_only, curation defaults + (exclude domains — UI editable, replacing the hardcoded EXCLUDED_DOMAINS fallback), + LQI thresholds, group_lights default. +- [ ] `repair-issues`: broken plan file references, orphaned layout entries, storage + migration failures → Repairs UI instead of silent logs. +- [ ] `system_health.py`: config rev, spaces/markers count, storage sizes. +- [ ] `exception-translations` + `icon-translations` where applicable. +- [ ] Frontend resource registration: adopt the community-consensus embedded-card pattern + end-to-end (we already do StaticPathConfig + storage-mode resource + `?v=` busting; + add resource cleanup on removal and document YAML-mode fallback). -## Phase 5 — UX/feature polish -- Configurable click actions: toggle for lights/outlets, long-press → more-info. -- Live-indication tuning (theme-aware colors, badges), light theme. -- Tooltips on touch devices (long-press), accessibility (keyboard, aria). -- LQI option: "bad signal" threshold, hiding the labels on non-zigbee instances. +## Phase 9 — Universality & flexibility (product depth) -## Phase 6 — Quality and publication -- Tests: pytest (config_flow, websocket_api, Store migrations) + hassfest/hacs action in CI; - frontend: vitest for rules/geometry utilities. -- Typing: strict TS interfaces for hass (custom-card-helpers or our own), mypy for python. -- Translations for integration+card: en + ru (translations/, card string localization). -- Quality scale bronze → silver checklist; PR into hacs/default; optionally a PR into - home-assistant/brands (the brand/ in the repo suffices for now). +- [ ] **Areas/floors registry integration**: import HA floors as spaces, suggest area + bindings from the registry, sync names (HA is moving this way — native Areas/Home + dashboard; riding the registry is our moat). +- [ ] **Curation without hardcode**: icon rules (`iconFor`) become data — user-editable + mapping (regex/domain/device_class → mdi icon) stored in config, shipping EN+RU + defaults; drop dacha-specific patterns from code. +- [ ] **Click actions** per device/domain: toggle / more-info / navigate / custom service + call (configurable, like standard card `tap_action`). +- [ ] **Theming**: respect light themes (currently dark-leaning), use HA theme variables + everywhere, optional per-space background color. +- [ ] Multi-instance question: keep single-instance (one house) but support **multiple + cards** with different default spaces (already works) — document as a decision. +- [ ] Plan formats: keep SVG/PNG/JPG/WebP; add max dimensions guidance; optional + auto-downscale on upload. +- [ ] More locales: extract i18n dictionaries to JSON so contributors can add languages + without touching TS. -## Open questions -- Publication name: "House Plan Card"? the `houseplan` domain is not taken in HACS default — verify. -- MIT license (package.json is already MIT) — add a LICENSE file. -- Plan formats: SVG preferred (vector, size), PNG support is mandatory. +## Phase 10 — Community & distribution + +- [ ] hacs/default PR #8995 through moderation (expect drafting for fixes). +- [ ] Demo GIF/video for README (the single biggest driver of adoption for dashboard cards). +- [ ] Forum post in the Floorplan category + Reddit r/homeassistant showcase once + the demo assets exist. +- [ ] GitHub: issue templates, discussions on, CONTRIBUTING.md (build/test in 5 minutes). +- [ ] Semantic versioning discipline; keep release notes user-facing (they show in HACS). + +## Explicit non-goals (for now) + +- 3D plans (.glb) — separate market (3Dash), heavy dependencies. +- Editing the plan image itself (walls/furniture drawing à la easy-floorplan) — we + consume existing plan images; the markup editor stays about *rooms/zones*, not drawing. +- Cloud anything. Config stays in `.storage`, local-first. diff --git a/docs/STATUS.md b/docs/STATUS.md new file mode 100644 index 0000000..cb9dd1e --- /dev/null +++ b/docs/STATUS.md @@ -0,0 +1,66 @@ +# Project status & session context + +> **Purpose of this file.** Cowork/AI sessions lose context (overflow, new session). +> This file is the **first thing to read** when resuming work. It captures the current +> state, where everything lives, and how to continue safely. +> +> **Documentation policy (mandatory):** every change is documented *in the same +> commit* — CHANGELOG entry for anything user-visible, STATUS.md for state changes +> (versions, publication, infrastructure), DEVELOPMENT.md for new gotchas, +> ARCHITECTURE.md for design changes, ROADMAP.md when plans move. + +## Snapshot (2026-07-05) + +| Item | State | +|---|---| +| Version | **v1.11.2** everywhere (manifest, const.py, package.json, CARD_VERSION) | +| GitHub | https://github.com/Matysh/houseplan-card — branch `main`, releases v1.9.3…v1.11.2 | +| CI | `.github/workflows/validate.yml` (hacs + hassfest + frontend + backend) — **fully green** since v1.11.1; `release.yml` auto-attaches the card bundle (needs `permissions: contents: write`, fixed) | +| HACS | Works as custom repository (id 1290210112 on the home instance). **Inclusion PR: https://github.com/hacs/default/pull/8995** (queue ≈2 months as of 2026-07) | +| Brands | Ships **inside the integration**: `custom_components/houseplan/brand/{icon,icon@2x,logo,logo@2x}.png` (HA ≥2026.3 local-brands mechanism). home-assistant/brands PR #10700 was auto-closed — that repo no longer accepts custom integrations | +| Home instance | ha.jbstudio.pro (SSH port 323, key `ha_jb`), deployed v1.11.2, installed *via HACS* (custom repo) — updates flow through HACS now | +| Localization | UI en/ru (`src/i18n.ts`), auto by `hass.locale` + `language` card option; codebase and docs are English-first (`README.ru.md` is the Russian copy) | +| Tests | 15 frontend (node:test on logic/rules) + 10 backend (pytest on validation.py) | + +## Recent milestones (details in CHANGELOG.md) + +- **v1.10.0** — audit & refactor: asyncio.Lock around all store writes (race fix, atomic + `expected_rev`), point `layout/update` instead of full `layout/set` (anti last-writer-wins), + new `layout/delete`, `safeUrl()` XSS guard, `fetchWithAuth`, KEY_HASS, streaming upload cap, + card split into modules (`styles.ts` / `types.ts` / `devices.ts`), dynamic spaces in the GUI + editor, dead code removed. +- **v1.11.0** — full English translation + en/ru UI localization. +- **v1.11.1** — brand images inside the integration; CI fully green for the first time. +- **v1.11.2** — Description textarea fix in the device dialog. + +## Where things live + +- **Source of truth:** the git repo (GitHub `main`). In a sandbox session: clone from GitHub or + from `houseplan-card.git.bundle` (kept fresh in the user folder root *and* in `houseplan-card/`). +- **User folder** `houseplan/houseplan-card/` — a file mirror of the repo (synced after every + commit; the mount cannot delete files, so a few stale artifacts linger — git is authoritative). +- **Production config:** server-side on the HA instance, `.storage/houseplan.config` + + `.storage/houseplan.layout` (backups `.bak-v1100` exist on the box). + +## Open items / watchlist + +1. **hacs/default PR #8995** — waiting for moderation (bot may draft the PR for minor fixes — + fix and re-ready; wrongly filled template ⇒ silent close). +2. GitHub PAT `houseplan-card-publish` (repo+workflow) expires ~2026-07-12; in sandbox + `~/.git-credentials`. Revoke after the HACS queue clears, or re-issue when needed. +3. Stale files on the mount that cannot be deleted from the sandbox: `src/data/` leftovers, + `brand_preview.png`, old nested bundle copies — ignore, git is authoritative. +4. Roadmap next: see ROADMAP.md (quality-scale conformance is the next big theme). + +## How to resume work in a fresh session (checklist) + +1. Read this file, then CHANGELOG.md (top entries), DEVELOPMENT.md (environment gotchas). +2. Restore the repo: `git clone /houseplan-card.git.bundle hpcN` in `/tmp` + (files from *previous* sandbox sessions in `/tmp` belong to `nobody` and are unreadable — + always clone into a fresh directory; `npm ci` again). +3. Deployment needs the `ha_jb` SSH key — ask the user to upload it (uploads are readable + only in the session they were uploaded in). +4. Build only in `/tmp` (never on the mount), `npm run build` (starts with `tsc --noEmit`), + md5-verify after every deploy, restart HA via + `nohup ha core restart >/dev/null 2>&1