diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..1690e17 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,45 @@ +name: Bug report +description: Something does not work as described in the documentation +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thanks for reporting! Remove camera addresses and passwords from anything you paste. + - type: input + id: version + attributes: + label: VisionState version + description: Settings → Apps → VisionState (or VisionState (beta)) + placeholder: "0.8.0" + validations: + required: true + - type: dropdown + id: kind + attributes: + label: Sensor kind + options: + - State sensor + - Object sensor + - Reading sensor + - Not about one sensor + validations: + required: true + - type: input + id: system + attributes: + label: Home Assistant version and hardware + placeholder: "2026.10.0, Raspberry Pi 4 / Intel NUC / Proxmox VM" + - type: textarea + id: what + attributes: + label: What happened, and what did you expect? + description: Steps to see it again help a lot. A screenshot is welcome. + validations: + required: true + - type: textarea + id: log + attributes: + label: Log + description: The app's Log tab, around the time it happened. + render: text diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3f91377 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Questions, ideas and shared meter readings + url: https://github.com/oleost/VisionState/discussions + about: Ask, suggest a feature, or share an export of readings (Quality tab → Export readings) to help improve reading. + - name: Security vulnerability + url: https://github.com/oleost/VisionState/security/advisories/new + about: Report it privately, not as an issue. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa68409..9c8ae1d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,12 +25,12 @@ jobs: cache: pip cache-dependency-path: visionstate/backend/requirements*.txt - run: pip install -r requirements-dev.txt - - name: Cache AI model + - name: Cache AI models (all of them, for tests/test_models.py) uses: actions/cache@v6 with: path: visionstate/backend/models - key: models-${{ hashFiles('visionstate/backend/visionstate/backbones.json', 'visionstate/backend/visionstate/detectors.json', 'visionstate/backend/visionstate/readers.json') }} - - run: python -m visionstate.backbones models + key: models-all-${{ hashFiles('visionstate/backend/visionstate/backbones.json', 'visionstate/backend/visionstate/detectors.json', 'visionstate/backend/visionstate/readers.json') }} + - run: python -m visionstate.backbones models --all - run: ruff check visionstate tests - run: ruff format --check visionstate tests - name: Types (pyright) diff --git a/CLAUDE.md b/CLAUDE.md index 66ca9fe..2b599ce 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,13 +3,41 @@ VisionState — a Home Assistant app that turns camera images into sensors, all locally: **states** the user teaches it (garage door open/closed; embedding model + small per-sensor classifier), **objects** found by a pretrained detector (people, cars, animals) and **readings** -(a number on a display, OCR). This file is the contributor guide for people and AI assistants. +(a number on a display or the wheels of a meter). This file is the contributor guide for people +and AI assistants. -- **Source of truth for scope and design decisions:** [`docs/SCOPE.md`](docs/SCOPE.md). - Update it when a decision changes. -- **Language:** everything in the repository (code, comments, UI text, docs, commit - messages, this file) is written in English. -- Public repository `github.com/oleost/VisionState`, Apache-2.0. Pull requests go to `beta`. +- **Language:** everything in the repository (code, comments, UI text, docs, commit messages) is + written in English. +- Public repository `github.com/oleost/VisionState`, Apache-2.0, owned by its maintainer (oleost). + The maintainer (and Claude working for them) commits straight to `beta`; everyone else opens a + pull request against `beta`. Branches and releases: [`docs/RELEASING.md`](docs/RELEASING.md). + +## Where things are written down + +Every fact has **one** home; other files link to it instead of repeating it. + +| What | Home | +|---|---| +| What the app does for a user, how to use it, the defaults a user sees | [`visionstate/DOCS.md`](visionstate/DOCS.md) (the app's *Documentation* tab) | +| The pitch, install, screenshots | [`README.md`](README.md) (and the short store text `visionstate/README.md`) | +| Design decisions and why, non-goals, roadmap, open ideas | [`docs/SCOPE.md`](docs/SCOPE.md) | +| Where the code is, how a check flows, data on disk, recipes for common changes | [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | +| Values of defaults, limits and tunables | `settings.py` and the model registries — docs name the setting, not its value (DOCS.md may state what the user sees) | +| Branches, channels, CI, release steps | [`docs/RELEASING.md`](docs/RELEASING.md) | +| How to work: principles, checks, testing, pitfalls | this file | +| The wheel reader model: data, licences, results, how to train it | [`tools/wheelreader/README.md`](tools/wheelreader/README.md) | +| The plan for better counter readings, and its status | [`docs/WHEEL_READER_PLAN.md`](docs/WHEEL_READER_PLAN.md) | +| Face recognition: models, licences, privacy, the plan and the decisions made building it | [`docs/FACE_RECOGNITION_PLAN.md`](docs/FACE_RECOGNITION_PLAN.md) | +| What changed in a version | `visionstate/CHANGELOG.md` (written at release, never edited afterwards) | +| Test image sources | `visionstate/backend/tests/assets/README.md` | + +- Update the home of a fact **in the same change** as the code; a change that makes a doc wrong is + not finished. +- No "as of version X" or "currently vN" status lines outside the CHANGELOG and the roadmap: they go + stale. Say what is, and let git say when. +- `tests/test_docs.py` (backend, runs in CI) checks every Markdown link, every repository path and + every `module.name` / `settings.X["key"]` in backticks against the code — a rename that leaves a + doc behind fails the tests. ## Principles (do not break these) @@ -19,205 +47,130 @@ classifier), **objects** found by a pretrained detector (people, cars, animals) - **Camera credentials never leak:** `redact.py` masks them in logs, errors and exports. - **No hard-coded defaults** outside `settings.py` / the registries; the UI reads them from `GET /api/v1/config`. -- Home Assistant state values stay lower-case keys (`open`, `car_parked`). +- Home Assistant state values stay lower-case keys (`open`, `car_parked`); unique IDs and topics + never change once released. - Marketing text (README, release notes) promotes VisionState on its own merits — no comparisons with other products. -## Layout and conventions - -- `visionstate/` is the Home Assistant app (build context of the Dockerfile). - - `backend/visionstate/settings.py` holds every backend default/tunable; import from there. - - **Map of the code: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)** — what runs where, how a - check flows, which files a common change touches. Keep it current when the structure changes. - - `backend/visionstate/engine/` is the runtime: `Runtime` (`runtime.py`) is put together from one - mixin per part (`models`, `checks`, `objects`, `reading`, `teaching`, `training`, `publishing`, - `history`, `reminders`) on `RuntimeBase` (`base.py`, the shared state); a part inherits the parts it uses. - `state.py` holds `SensorConfig`/`LiveState`, `logic.py` the pure decisions. New engine code goes - into the part it belongs to; a long-running loop must survive any exception (log it once, try - again) — only `CancelledError` may end it. - - `backend/visionstate/backbones.json` (state sensors), `detectors.json` (object sensors) and - `readers.json` (reading sensors; also the wheel readers for mechanical counters, trained with - `tools/wheelreader` — see its README) are the model registries. Entries are never changed or - removed once released (a new model gets a new id); `python -m visionstate.backbones ` - downloads every bundled model of all three. - - Sensor kinds (`settings.SENSOR_KINDS`): `single_state`, `objects` and `reading`; the UI's tabs per kind - are in `ui.ts` (`TABS_BY_KIND`). New features must say which kind(s) they apply to. - - The History page and every sensor's History tab are one view (`lib/components/history/`, - a row component per kind); its filter is in the page URL (`lib/history.ts`). On the server - `api/history.py` turns every filter into SQL in one place (`history_query`); a new filter - goes there, a new event into `settings.HISTORY_EVENTS` + `EVENT_ROWS` + `ui.ts HISTORY_EVENTS`. - - Object sensors can be taught (`teach.py`, `api/teach.py`, `settings.TEACH`): taught boxes are - `sample` rows with `object_label`; own labels live in `objects["custom"]`. The detector is never - retrained; DINOv2 (always loaded) compares boxes with the taught ones. - - `frontend/src/lib/tokens.css` holds all colours/type/spacing; `ui.ts` holds UI constants; - `api.ts` is the only place that builds API URLs; `router.svelte.ts` defines app paths. - - The UI reads sensor defaults, limits and palette from `GET /api/v1/config`. -- Backend checks: `cd visionstate/backend && .venv/Scripts/python -m pytest -q && .venv/Scripts/ruff check visionstate tests && .venv/Scripts/ruff format visionstate tests` - (tests need the model: `python -m visionstate.backbones models`), and the types: - `.venv/Scripts/pyright --pythonpath .venv/Scripts/python.exe` (0 errors; CI runs it). CI measures - coverage and fails below 88 %; add `--cov=visionstate --cov-report=term-missing:skip-covered` to see - what is not tested. -- Frontend checks: `cd visionstate/frontend && npm run check && npm run build`. -- **UI testing routine** (every UI change, before a beta release) — many users run Home Assistant - on phones/tablets: - - Automated: `cd visionstate/frontend && npm run build && VS_PYTHON=../backend/.venv/Scripts/python npm run e2e` - (Playwright, `e2e/`; also runs in CI; once: `npx playwright install chromium webkit`). It starts the backend + `scripts/fake_camera.py`, - seeds one sensor of each kind (a trained state sensor, an object sensor on a real photo and - reading sensors on a drawn display and a drawn mechanical counter), and runs every page on **desktop** (1440×900, mouse), - **mobile** (Pixel 7, real touch via CDP) and **iphone** (iPhone 14, WebKit like the Home Assistant - app on iOS; gestures with the mouse): no console errors, no sideways scrolling, no text - running out of buttons or cards, plus the wizard (all three kinds), region editor gestures, - labelling, review, boxes, teaching an object sensor and readings. Add a test - for every new page or gesture. - - Look at the full-page screenshots in `test-results/pages/{desktop,mobile,iphone}/` after UI changes, - for every page in all three projects. - - Then try the change by hand in a real browser (Claude: the Chrome tools) — passing tests are not - enough: start `scripts/fake_camera.py 8198` and the backend (`VISIONSTATE_PORT`, `_DATA`, `_MEDIA`, - `_FRONTEND=frontend/dist`, `_BUNDLED_MODELS` as in `playwright.config.ts`; a copy of the last e2e - run's `data` and `media` folders gives seeded sensors), use the changed screens at desktop width - and at ~390 px, and check the console. Reload after a rebuild (a hash-only navigation keeps the old page). - A Chrome window does not get narrower than 500 px (and a maximised one not at all): for a real - phone width, open any same-origin URL (e.g. `/api/v1/status`) and replace the page with - `