Compare commits

..
Author SHA1 Message Date
Sergey Matyunin 50099acc75 fix: allow stable promotion of published beta history
Validate / provenance (push) Successful in 5m46s
Validate / process-gate (push) Failing after 5m56s
Validate / hacs (push) Failing after 20s
Validate / hassfest (push) Failing after 14s
Validate / frontend (push) Successful in 13m53s
Validate / backend (push) Failing after 10m41s
Validate / golden (push) Failing after 9m40s
Validate / performance_smoke (push) Failing after 17m26s
Validate / smoke (push) Failing after 34m54s
Full Performance / performance (push) Failing after 1h54m13s
Issue: #130
User-Visible: no
2026-08-13 22:59:23 +03:00
Sergey Matyunin a282f850af build: promote v1.63.0
Issue: #129
User-Visible: yes
2026-08-13 22:47:48 +03:00
Sergey Matyunin d7f3bb8119 test: accept v1.63.0-beta.2 golden baselines
Issue: #123
User-Visible: no
Release: v1.63.0-beta.2
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/31734606270
2026-08-13 22:26:38 +03:00
Sergey Matyunin 5c6ab8ea9b Release v1.63.0-beta.2 candidate
Issue: #123
User-Visible: yes
2026-08-13 22:11:02 +03:00
Sergey Matyunin fda4893f0c Merge updated dev for v1.63.0 2026-08-13 22:10:28 +03:00
Matysh 888e90450a perf: make review scope and ceremony fit the size of the task
The owner's report: the process works but every stage takes a long time even on
simple bugs. Two causes, and neither was the one that first comes to mind.

The reviewer ran everything regardless. On #89 it installed Chromium, ran all 127
smoke files and a full golden capture — right for a task rated 10/10 for
complexity, absurd for a bug about a room divider. Full suites are the pre-beta
gate; the review now runs typecheck, unit and build always, and smokes, golden,
pytest or performance only where the diff and the AC call for them. The price of
narrowing it is honesty: the reviewer must list which gates it ran, which it did
not, and why, so a skipped gate is a visible decision rather than a silent one.

The reviewer also built its own environment out of model turns, with no npm cache
and no browser cache, paid for from the same forty-five minutes. The workflow now
installs dependencies and Chromium as ordinary cached steps, after switching to
the task branch so the lockfile is the branch's own.

Second, ceremony did not scale down. The light track makes a spec cheap; the new
trivial track does without one — S2-analysis straight to S5-ready, no spec review,
AC in the issue body. It is deliberately hard to qualify for: a bug on one surface,
no new UX contract, no migration, no i18n, no perf or touch effect, three checkable
AC at most, and expected behaviour already on record. Nothing left to decide is the
criterion that holds the whole thing up, and it cannot be met by feeling sure.

Code review is never skipped on either track. It is what stands in for testing
here, so it is the one stage speed may not buy.

Issue: #127
Issue: #128
User-Visible: no
2026-08-13 22:07:42 +03:00
Sergey Matyunin b2263a6551 Merge main into dev for v1.63.0 2026-08-13 22:05:35 +03:00
Matysh 565f518dcd perf: make review scope and ceremony fit the size of the task
The owner's report: the process works but every stage takes a long time even on
simple bugs. Two causes, and neither was the one that first comes to mind.

The reviewer ran everything regardless. On #89 it installed Chromium, ran all 127
smoke files and a full golden capture — right for a task rated 10/10 for
complexity, absurd for a bug about a room divider. Full suites are the pre-beta
gate; the review now runs typecheck, unit and build always, and smokes, golden,
pytest or performance only where the diff and the AC call for them. The price of
narrowing it is honesty: the reviewer must list which gates it ran, which it did
not, and why, so a skipped gate is a visible decision rather than a silent one.

The reviewer also built its own environment out of model turns, with no npm cache
and no browser cache, paid for from the same forty-five minutes. The workflow now
installs dependencies and Chromium as ordinary cached steps, after switching to
the task branch so the lockfile is the branch's own.

Second, ceremony did not scale down. The light track makes a spec cheap; the new
trivial track does without one — S2-analysis straight to S5-ready, no spec review,
AC in the issue body. It is deliberately hard to qualify for: a bug on one surface,
no new UX contract, no migration, no i18n, no perf or touch effect, three checkable
AC at most, and expected behaviour already on record. Nothing left to decide is the
criterion that holds the whole thing up, and it cannot be met by feeling sure.

Code review is never skipped on either track. It is what stands in for testing
here, so it is the one stage speed may not buy.

Issue: #127
Issue: #128
User-Visible: no
2026-08-13 21:59:55 +03:00
Sergey Matyuninandclaude[bot] 02e0c9801d Fix corner split smoke geometry input
Issue: #123
User-Visible: no
2026-08-13 18:55:49 +00:00
claude[bot]andclaude[bot] e93c405b13 docs: code review document for #123
Issue: #123
User-Visible: no
2026-08-13 18:55:49 +00:00
Sergey Matyuninandclaude[bot] 955de3e69c Fix corner split exterior walls
Issue: #123
User-Visible: yes
2026-08-13 18:55:49 +00:00
claude[bot]andclaude[bot] 7af4146614 docs: review document for #123
Issue: #123
User-Visible: no
2026-08-13 18:55:49 +00:00
Sergey Matyuninandclaude[bot] a43602934c Specify corner split wall geometry
Issue: #123
User-Visible: no
2026-08-13 18:55:49 +00:00
Matysh 516257e322 perf: make review scope and ceremony fit the size of the task
The owner's report: the process works but every stage takes a long time even on
simple bugs. Two causes, and neither was the one that first comes to mind.

The reviewer ran everything regardless. On #89 it installed Chromium, ran all 127
smoke files and a full golden capture — right for a task rated 10/10 for
complexity, absurd for a bug about a room divider. Full suites are the pre-beta
gate; the review now runs typecheck, unit and build always, and smokes, golden,
pytest or performance only where the diff and the AC call for them. The price of
narrowing it is honesty: the reviewer must list which gates it ran, which it did
not, and why, so a skipped gate is a visible decision rather than a silent one.

The reviewer also built its own environment out of model turns, with no npm cache
and no browser cache, paid for from the same forty-five minutes. The workflow now
installs dependencies and Chromium as ordinary cached steps, after switching to
the task branch so the lockfile is the branch's own.

Second, ceremony did not scale down. The light track makes a spec cheap; the new
trivial track does without one — S2-analysis straight to S5-ready, no spec review,
AC in the issue body. It is deliberately hard to qualify for: a bug on one surface,
no new UX contract, no migration, no i18n, no perf or touch effect, three checkable
AC at most, and expected behaviour already on record. Nothing left to decide is the
criterion that holds the whole thing up, and it cannot be met by feeling sure.

Code review is never skipped on either track. It is what stands in for testing
here, so it is the one stage speed may not buy.

Issue: #127
Issue: #128
User-Visible: no
2026-08-13 21:51:42 +03:00
Matysh 9177c9a944 perf: make review scope and ceremony fit the size of the task
The owner's report: the process works but every stage takes a long time even on
simple bugs. Two causes, and neither was the one that first comes to mind.

The reviewer ran everything regardless. On #89 it installed Chromium, ran all 127
smoke files and a full golden capture — right for a task rated 10/10 for
complexity, absurd for a bug about a room divider. Full suites are the pre-beta
gate; the review now runs typecheck, unit and build always, and smokes, golden,
pytest or performance only where the diff and the AC call for them. The price of
narrowing it is honesty: the reviewer must list which gates it ran, which it did
not, and why, so a skipped gate is a visible decision rather than a silent one.

The reviewer also built its own environment out of model turns, with no npm cache
and no browser cache, paid for from the same forty-five minutes. The workflow now
installs dependencies and Chromium as ordinary cached steps, after switching to
the task branch so the lockfile is the branch's own.

Second, ceremony did not scale down. The light track makes a spec cheap; the new
trivial track does without one — S2-analysis straight to S5-ready, no spec review,
AC in the issue body. It is deliberately hard to qualify for: a bug on one surface,
no new UX contract, no migration, no i18n, no perf or touch effect, three checkable
AC at most, and expected behaviour already on record. Nothing left to decide is the
criterion that holds the whole thing up, and it cannot be met by feeling sure.

Code review is never skipped on either track. It is what stands in for testing
here, so it is the one stage speed may not buy.

Issue: #127
Issue: #128
User-Visible: no
2026-08-13 21:33:36 +03:00
Matysh 8a3f6efa0a fix: the review document is published even without a task branch
Issues labelled before the pipeline existed keep their spec straight in dev and
have no issue/NN branch. The publish step quietly exited zero for them, so the
verdict would arrive as a comment and the analysis behind it would be thrown
away — the fifth instance today of a step reporting success by doing nothing.

The document now goes wherever the spec itself lives: the task branch when there
is one, dev otherwise. Publishing also survives dev moving on while the review
ran, which takes up to forty-five minutes, by rebasing once before it gives up.

Four issues are waiting on this — #12, #30, #44 and #52 — each with a spec in dev,
a status label applied during the bulk pass in August and a review that never ran
because nothing was there to raise the event.

Issue: #114
User-Visible: no
2026-08-13 21:11:41 +03:00
Matysh be7d6b9706 fix: the review document is published even without a task branch
Issues labelled before the pipeline existed keep their spec straight in dev and
have no issue/NN branch. The publish step quietly exited zero for them, so the
verdict would arrive as a comment and the analysis behind it would be thrown
away — the fifth instance today of a step reporting success by doing nothing.

The document now goes wherever the spec itself lives: the task branch when there
is one, dev otherwise. Publishing also survives dev moving on while the review
ran, which takes up to forty-five minutes, by rebasing once before it gives up.

Four issues are waiting on this — #12, #30, #44 and #52 — each with a spec in dev,
a status label applied during the bulk pass in August and a review that never ran
because nothing was there to raise the event.

Issue: #114
User-Visible: no
2026-08-13 21:05:17 +03:00
Matysh 2fd042a7de feat: an outsider's issue is worked like any other once admitted
The guard refused to review any issue the owner had not filed himself. The rule
was meant to keep malformed outside reports out of the pipeline, but it checked at
every step instead of at the entrance, and it duplicated a guarantee the platform
already gives: only someone with write access can apply a label. Applying the
first status label is the owner's explicit decision, and it is the only place the
question belongs.

So the author check is gone. While an issue carries no status label it sits
outside the process and the invariants do not apply; once labelled, the task is in
flight and who filed it stops mattering.

The old rule also cost real work. On #123 an outside bug report had been analysed
and specified before the guard turned it away in nine seconds, and the remedy on
offer was to refile the same thing as the owner's own issue.

Issue: #114
User-Visible: no
2026-08-13 20:41:36 +03:00
Matysh d7e2c4d4f0 fix: the guard says why it refused, in the issue
A review label promises work. When the guard declined it wrote the reason to the
run log and nothing else, so the issue sat in a status nobody was acting on and
nobody could tell. #123 showed it: an outside reporter's issue was walked up to
S4-spec-review, the guard refused in nine seconds because only the owner's issues
enter the process, and the issue itself said not a word.

Refusals that a human can act on now become a comment: wrong author, blocked,
review-4. Only when a stage was actually recognised, so an unrelated label change
stays silent.

This is the same defect as the merge conflict that left the label untouched, seen
from the other side. The pattern is worth naming: doing nothing quietly is the
most expensive thing a pipeline can do.

Issue: #114
User-Visible: no
2026-08-13 20:30:15 +03:00
Matysh d1be6891b2 fix: a review run always moves the label, conflict or not
Validate / hacs (push) Failing after 55s
Validate / hassfest (push) Failing after 13s
Validate / frontend (push) Successful in 6m16s
Validate / backend (push) Failing after 8m39s
Validate / provenance (push) Successful in 37s
Validate / golden (push) Failing after 8m4s
Validate / smoke (push) Failing after 27m15s
Validate / performance_smoke (push) Failing after 14m5s
Full Performance / performance (push) Failing after 1h15m11s
A green code review whose merge conflicted used to leave the label where it was.
That is a dead end: the author waits for the label to change, so it polled thirty
times and reported the limit as exhausted — on a task the reviewer had already
passed. The verdict existed and nobody could act on it.

The merge step no longer fails the job. It reports whether it merged, and a green
review that did not merge sends the task back to S6-in-progress, because the work
did return to the author — a rebase rather than a code fix, and the comment says
so and says the verdict still stands.

The invariant is now stronger and worth stating plainly: after a review run the
label always changes. A pipeline whose state can stall silently is worse than one
that reports the wrong state loudly.

Issue: #114
User-Visible: no
2026-08-13 16:58:18 +03:00
Matysh 9be81c1413 fix: repair the line continuation in the failure handler
The step that comments on the issue when a review run dies carried a literal
backslash instead of a line continuation, so gh received four arguments and
--repo ran as a command of its own. The handler for failures would itself have
failed, silently, and only when something had already gone wrong.

bash -n does not catch this: the syntax is valid, the meaning is not. Checking
run blocks now also means looking for a doubled backslash at end of line.

Issue: #114
User-Visible: no
2026-08-13 16:16:39 +03:00
Matysh fafeca4540 fix: count review cycles per stage, not across the whole issue
The guard counted every verdict comment on the issue, so a spec-review verdict
consumed a cycle from the code-review budget. On #89 the first code review came
out as r2/4. With two spec cycles the second code review would have hit review-4
after a single fix — the limit would have fired on a task nobody had reviewed
twice.

The stage is now resolved first and only its own verdicts are counted, recognised
by the review document named in the comment. If the document is missing the
verdict is not counted: undercounting grants an extra cycle, overcounting would
stop the work early, and of the two mistakes the recoverable one wins.

Issue: #114
User-Visible: no
2026-08-13 16:08:20 +03:00
Matysh 3ade633538 ci: merge into dev before setting S8-merged
The label asserts the code is in dev. The workflow used to set it on a green
code review while the commits were still only on the task branch, so between
the verdict and the author's merge the state machine stated something untrue —
which is exactly what happened on #104.

The merge now runs inside the pipeline, before the label. A conflict leaves
the issue in S7-code-review and comments instead.

Issue: #114
User-Visible: no
2026-08-13 13:20:26 +03:00
Matysh 68596a75a0 ci: the reviewer writes a review document to the task branch
PROCESS.md wants a review document in docs/reviews/; the CI reviewer could
only leave a comment, and flagged the gap itself. It may now write there.

What lands in the commit is decided by the workflow, not by the model: every
path outside docs/reviews/ is reverted before staging, and the commit carries
the usual trailers so the provenance gate accepts it.

Issue: #114
User-Visible: no
2026-08-13 13:04:26 +03:00
Matysh 65a86db122 fix(ci): repair the failure comment step
The multi-line --body started at column zero, which ends the YAML block
scalar. The parser silently truncated the run script and left an unclosed
double quote, so the whole workflow became unusable and blocked the code
review on #104.

The body now goes through a heredoc. Validating YAML alone did not catch
this; every run block is checked with bash -n from now on.

Issue: #114
User-Visible: no
2026-08-13 12:57:04 +03:00
Matysh a29df12e0b ci: raise the turn limit, bound the run by time instead
The r2 spec review on #104 produced a complete green verdict and then failed
on --max-turns 40 at turn 43, so the label step never ran and the transition
had to be reconciled by hand. Forty was a guess; a review that reads SCOPE,
AGENTS, PROCESS, the issue thread and the spec exceeds it routinely, and a
code review that also runs gates needs far more.

The real guard against a runaway run is the job timeout, not the turn count.

Issue: #114
User-Visible: no
2026-08-13 12:37:14 +03:00
Matysh 9146b4c357 ci: fix OIDC permission and review the issue branch
The first live run failed with "Could not fetch an OIDC token": the action
needs id-token: write to authenticate the GitHub App.

The reviewer also checked out dev, where the material under review does not
exist yet — specs and code are committed to issue/<NN>-slug. The job now
switches to that branch when it is pushed, and warns loudly when it is not.

Issue: #114
User-Visible: no
2026-08-13 12:02:31 +03:00
Matysh 9ad01be813 ci: event-driven process pipeline for spec and code review
Adds .github/workflows/process.yml. A status label change is the trigger:
S4-spec-review runs the spec review, S7-code-review runs the code review,
and the verdict decides the next label. Only a green verdict advances;
yellow and red return the task to its author. Cycle limits (4, or 2 on the
light track) are counted from the verdicts already posted on the issue.

Labels are moved with HP_PROCESS_TOKEN, not GITHUB_TOKEN, so the change
emits an event and the chain continues.

Issue: #114
User-Visible: no
2026-08-13 11:34:01 +03:00
43 changed files with 314 additions and 220 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ while read -r local_ref local_sha remote_ref remote_sha; do
echo "process-gate: $local_ref, диапазон ${base}..${local_sha}" >&2
# shellcheck disable=SC2086
if ! node "$gate" --range "${base}..${local_sha}" $issues_flag >&2; then
if ! node "$gate" --range "${base}..${local_sha}" --target-ref "$remote_ref" $issues_flag >&2; then
status=1
fi
done
+88 -12
View File
@@ -48,6 +48,7 @@ jobs:
BLOCKED: ${{ contains(github.event.issue.labels.*.name, 'blocked') }}
EXHAUSTED: ${{ contains(github.event.issue.labels.*.name, 'review-4') }}
SMALL: ${{ contains(github.event.issue.labels.*.name, 'small') }}
TRIVIAL: ${{ contains(github.event.issue.labels.*.name, 'trivial') }}
NUM: ${{ github.event.issue.number }}
run: |
# Этап определяется первым: от него зависит, какие вердикты считать.
@@ -58,8 +59,9 @@ jobs:
*) echo "метка $LABEL конвейер не запускает" ;;
esac
# Лимит циклов: 4 обычный, 2 на лёгком треке (PROCESS.md §4).
limit=4; [ "$SMALL" = "true" ] && limit=2
# Лимит циклов: 4 обычный, 2 на лёгком и коротком треке (PROCESS.md §4).
limit=4
if [ "$SMALL" = "true" ] || [ "$TRIVIAL" = "true" ]; then limit=2; fi
# Счётчик считает вердикты ТОЛЬКО своего этапа. Раньше он брал все
# подряд, и вердикт по ТЗ съедал цикл из бюджета код-ревью: на #89
@@ -134,8 +136,15 @@ jobs:
fetch-depth: 0
ref: dev
# Окружение готовит workflow, а не модель своими ходами. Раньше промпт
# велел ревьюеру самому выполнить `npm ci`: минуты уходили на установку без
# кэша, платились из бюджета 45 минут и из лимитов подписки, а ходы модели
# тратились на работу инфраструктуры. В validate.yml кэш стоит на всех
# тяжёлых job, здесь его не было.
- uses: actions/setup-node@v4
with: { node-version: 22 }
with:
node-version: 22
cache: npm
# Материал ревью живёт в ветке задачи: ТЗ в docs/specs/ и код коммитятся
# в issue/<NN>-slug. Если ветка запушена — переключаемся на неё, иначе
@@ -156,6 +165,24 @@ jobs:
echo "МАТЕРИАЛ НЕ ЗАПУШЕН" >> "$GITHUB_STEP_SUMMARY"
fi
# Зависимости ставятся ПОСЛЕ переключения на ветку задачи: lockfile мог
# измениться именно в ней, и установка по копии из dev дала бы не то дерево.
- name: Установить зависимости
run: npm ci
# Браузер нужен не всякому ревью (см. правило выбора гейтов в промпте),
# но когда нужен — качать его заново дороже, чем держать в кэше.
- name: Кэш браузеров Playwright
id: pw
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Установить Chromium
if: steps.pw.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium
- name: Review
id: review
uses: anthropics/claude-code-action@v1
@@ -205,10 +232,38 @@ jobs:
По каждому AC: либо он доказан автотестом и ты убедился, что тест
умеет падать, либо разобран по коду с явной записью «проверено
чтением, не исполнением». «Verified» без названной команды и её
результата доказательством не является. Зависимостей в рабочей
копии нет: перед гейтами выполни `npm ci`. Проверь трейлеры Issue и
User-Visible, при User-Visible: yes — правки в оба changelog в том же
коммите.
результата доказательством не является. Зависимости уже установлены
workflow, Chromium тоже — `npm ci` выполнять не нужно. Проверь
трейлеры Issue и User-Visible, при User-Visible: yes — правки в оба
changelog в том же коммите.
**Объём гейтов соразмерен задаче.** Прогонять весь набор на каждой
правке — не тщательность, а потеря времени: полные наборы это
предрелизный гейт (PROCESS.md §8), а не гейт ревью.
Всегда, они дешёвые:
`npx tsc --noEmit`, `npm test`, `npm run build` со сверкой трёх
копий бандла.
По необходимости, и «необходимость» определяется diff'ом и AC:
- браузерные смоки `demo/smoke_*.mjs` — названные в AC плюс
относящиеся к тронутым поверхностям. Их 127; прогон всех уместен
только когда задача действительно задевает всё;
- `npm run golden:verify` — если diff может изменить видимый
результат: рендер, геометрия, стили, слои;
- `python -m pytest tests_backend -q` — если тронут
`custom_components/**/*.py`;
- performance-профили — если названы в AC либо тронуты
чувствительные к перфу пути.
Дисциплина «тест должен уметь падать» не отменяется, но применяется к
тем тестам, которые ты прогонял.
**В комментарии обязателен перечень: какие гейты прогнал, какие нет и
почему.** Это условие честности такого сужения: непрогнанный гейт
становится видимым решением, а не молчаливым пропуском. Раздел «чего
не проверял» в документе ревью — не формальность, а главный его
раздел на коротких задачах.
Ты НЕ правишь ни ТЗ, ни продуктовый код. Только оцениваешь.
@@ -249,14 +304,21 @@ jobs:
BRANCH: ${{ steps.branch.outputs.name }}
NUM: ${{ github.event.issue.number }}
run: |
# Ветки задачи может не быть: у задач, размеченных до появления
# конвейера, ТЗ лежит прямо в dev. Раньше шаг в этом случае молча
# выходил с нулём, и разбор ревью терялся — оставался только вердикт
# комментарием. Это тот же тихий отказ: шаг сообщал об успехе тем, что
# ничего не сделал. Документ ложится туда же, где лежит само ТЗ.
target="${BRANCH:-dev}"
if [ -z "$BRANCH" ]; then
echo "ветки задачи нет — документ некуда класть"; exit 0
echo "::warning::ветки задачи нет — документ ревью ляжет в dev"
fi
git checkout -- . 2>/dev/null || true
git clean -fd -e docs/reviews -e node_modules >/dev/null 2>&1 || true
git add docs/reviews 2>/dev/null || true
if git diff --cached --quiet; then
echo "документ ревью не создан"; exit 0
echo "::warning::документ ревью не создан"
exit 0
fi
git -c user.name="claude[bot]" \
-c user.email="209825114+claude[bot]@users.noreply.github.com" \
@@ -266,9 +328,23 @@ jobs:
Issue: #$NUM
User-Visible: no
EOF
git push -q "https://x-access-token:$TOKEN@github.com/${{ github.repository }}" \
"HEAD:$BRANCH"
echo "документ опубликован в $BRANCH"
# Публикация в dev идёт из детачнутого состояния поверх ветки задачи
# либо dev, поэтому push нужен с явным перебазированием при гонке:
# dev мог уйти вперёд, пока шло ревью — оно длится до 45 минут.
if ! git push -q "https://x-access-token:$TOKEN@github.com/${{ github.repository }}" \
"HEAD:$target"; then
git fetch -q origin "$target"
if ! git -c user.name="claude[bot]" \
-c user.email="209825114+claude[bot]@users.noreply.github.com" \
rebase "origin/$target"; then
git rebase --abort || true
echo "::error::документ ревью не удалось опубликовать в $target: конфликт"
exit 0
fi
git push -q "https://x-access-token:$TOKEN@github.com/${{ github.repository }}" \
"HEAD:$target"
fi
echo "документ опубликован в $target"
- name: Решение по вердикту
id: decide
+1
View File
@@ -51,6 +51,7 @@ jobs:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.sha }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
TARGET_REF: ${{ github.ref }}
# Публичный репозиторий: штатного токена хватает на чтение issue.
GH_TOKEN: ${{ github.token }}
run: |
+9
View File
@@ -41,6 +41,15 @@ of a status and `rejected` on a closed issue. Exactly one `S*` label per open
issue. [GitHub Projects (v2)](https://github.com/users/Matysh/projects/1) is a
human-facing view synchronised from the labels, not the source of truth.
Two shortcuts exist for small work. `small` — the light track: the spec lives in
the issue body and its review is a comment. `trivial` — the short track: no spec
stage at all, `S2-analysis` straight to `S5-ready`, with the AC written into the
issue body first. `trivial` requires a bug confined to one surface with no new UX
contract, no migration, no i18n, no perf or touch impact, at most three checkable
AC, **and expected behaviour already on record** — nothing left to decide. Code
review is never skipped on either track; it is what stands in for testing.
`PROCESS.md` §5 and §5.1 hold the criteria.
An issue filed by an outsider is worked exactly like one of the owner's own, once
the owner has decided to take it. The check sits **at the entrance**, not on every
step: while an issue carries no status label it is outside the process and the
+59 -4
View File
@@ -70,7 +70,8 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
→ S6-in-progress → S7-code-review ⟲ → S8-merged → закрыт при выпуске беты
служебные: blocked (поверх статуса) rejected (закрыт)
⟲ — возврат на правки, не более 4 циклов (§4), на лёгком треке 2
⟲ — возврат на правки, не более 4 циклов (§4), на лёгком и коротком треке 2
короткий трек (`trivial`, §5.1) идёт S2-analysis → S5-ready, минуя S3 и S4
```
Переходы `S4-spec-review` и `S7-code-review` выполняются **автоматически**: метка
@@ -294,6 +295,42 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
модуль) — метка `small` снимается, issue возвращается в `S3-spec` и получает
нормальный файл ТЗ. Это не провал, это ранняя диагностика.
### 5.1 Короткий трек (метка `trivial`)
Решение владельца 2026-08-13, issue #128. Лёгкий трек делает ТЗ дешёвым; короткий
обходится без него совсем.
**Маршрут:** `S1-new` → `S2-analysis` → `S5-ready` → `S6-in-progress` →
`S7-code-review` → `S8-merged`. Стадии `S3-spec` и `S4-spec-review` пропускаются.
`S2-analysis` остаётся: это комментарий, а не прогон CI, и именно там владелец
решает приоритет и ценность. AC пишет автор в теле issue при переводе в
`S5-ready` — до перехода, иначе ревьюеру нечего будет сверять.
**Критерии, все обязательны:**
- тип `bug`;
- правка ограничена одной поверхностью, нового UX-контракта нет;
- нет миграции конфига, новых ключей i18n, влияния на перф и touch;
- AC выражаются тремя проверяемыми утверждениями или меньше;
- **ожидаемое поведение уже зафиксировано** — в `docs/USER-GUIDE.ru.md`, в
каноническом документе подсистемы либо однозначно в самом отчёте. Решать нечего.
Если есть что решать, это `S3-spec`, и никакая экономия этого не отменяет.
Метка ставится в `S2-analysis` вместе с остальными оценками, одним комментарием,
где владелец утверждает и приоритет.
**Что не упрощается:** issue, оценка, статусы, трейлеры, changelog и **код-ревью**.
Лимит циклов код-ревью — 2, как на лёгком треке.
Если по ходу выясняется, что критерий нарушен, метка снимается и issue уходит в
`S3-spec` за нормальным ТЗ. Как и на лёгком треке, это не провал, а ранняя
диагностика.
**Чем этот трек опасен.** Он убирает единственное место, где решение проверялось
до написания кода. Признак «решать нечего» держит всю конструкцию, и его нельзя
подтверждать ощущением — только ссылкой на уже зафиксированное поведение.
---
## 6. Роли
@@ -431,6 +468,17 @@ npm run golden:verify # если менялся визуал
python -m pytest tests_backend -q # py3.13, если менялся бэкенд
```
**Объём гейтов на код-ревью соразмерен задаче** (issue #127). Всегда:
`typecheck`, `npm test`, `npm run build` со сверкой трёх копий бандла. По
необходимости, определяемой diff'ом и AC: браузерные смоки (их 127 — прогон всех
уместен только когда задача задевает всё), `golden:verify` при изменении видимого
результата, `pytest tests_backend` при правках в Python, performance-профили при
названном в AC влиянии. **Полные наборы — предрелизный гейт, а не гейт ревью.**
Условие честности такого сужения: ревьюер обязан перечислить, какие гейты прогнал,
какие нет и почему. Непрогнанный гейт становится видимым решением, а не молчаливым
пропуском.
**Гейт беты** (условие закрытия issue): CI Validate зелёный на точном SHA тега.
Часть гейтов запускается только здесь, то есть **после** пройденного код-ревью.
@@ -464,8 +512,8 @@ Project v2 остаётся человеческим представление
| `blocked` | Ждём внешнего или владельца, **поверх** статусной метки |
| `rejected` | Отклонено, issue закрыт |
Модификаторы: `small` (лёгкий трек, сложность ≤3), `hotfix`, `process`,
`review-4`; приоритет `P1`/`P2`/`P3`; тип `bug`/`feature`/`tech-debt`.
Модификаторы: `small` (лёгкий трек, сложность ≤3), `trivial` (короткий трек,
§5.1), `hotfix`, `process`, `review-4`; приоритет `P1`/`P2`/`P3`; тип `bug`/`feature`/`tech-debt`.
Тематические метки (`polish`, `infra`, `tests`, `docs`, `security`, `vacuum`)
ортогональны процессу.
@@ -560,7 +608,7 @@ Project v2 остаётся человеческим представление
{`S5-ready`, `S6-in-progress`, `S7-code-review`, `S8-merged`}; закрытый,
недоступный или помеченный `blocked` — отказ (**fail closed**).
Две оговорки к проверке 8, обе выяснились при реализации.
Три оговорки к проверке 8 выяснились при реализации.
**`S8-merged` входит в множество**, хотя по смыслу задача уже принята. Причина
механическая: конвейер (§10.4) сливает ветку в `dev` **раньше**, чем ставит метку,
@@ -573,6 +621,13 @@ Validate стартует от этого push и успевает прочит
документ ревью: он ложится в ветку задачи, пока та в `S4-spec-review` или
`S7-code-review`, то есть заведомо вне рабочего множества.
**При продвижении в `main` не перепроверяются коммиты, уже достижимые из
prerelease-тега.** После выпуска беты их issue по §2.8 должны быть закрыты, а
stable fast-forward снова включает эти коммиты в диапазон `old-main..candidate`.
Pre-push передаёт целевую remote ref через `--target-ref`, а Validate — через
`TARGET_REF`; оба исключают только уже опубликованную prerelease-историю. Любой
post-beta коммит остаётся в проверке и по закрытому issue отклоняется fail-closed.
Не реализовано и остаётся долгом:
9. `npm run release:prerelease -- --issues=…` не проверяет, есть ли у issue
+1 -1
View File
@@ -45,7 +45,7 @@ PLAN_ORPHAN_TTL_S = 3600
SCHEDULED_GRACE_S = 30 * 24 * 3600
FILES_DIR = "houseplan/files"
CONF_ADMIN_ONLY = "admin_only"
VERSION = "1.63.0-beta.1"
VERSION = "1.63.0"
# Portable backup format. This is deliberately independent from the Home
# Assistant Store version above: storage migrations and files exported by a
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -16,5 +16,5 @@
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
"requirements": [],
"single_config_entry": true,
"version": "1.63.0-beta.1"
"version": "1.63.0"
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

+25 -22
View File
@@ -1,29 +1,32 @@
{
"schema": 1,
"matrixVersion": 17,
"acceptedAt": "2026-08-13T14:30:05.929Z",
"sourceFingerprint": "66f31850eafc963848acdc4e56e363a364bbb9b189bf4ade73d3c111ae2fc375",
"matrixVersion": 18,
"acceptedAt": "2026-08-13T19:26:19.387Z",
"sourceFingerprint": "27fda3d75e4cda95b9d85a9481f15ccd44b96d8c9d3311e0476718d36e2588c5",
"chromium": "151.0.7922.34",
"scenarios": {
"isometric-geometry-view-dark": "6db601f322fe55e33a1a89fe62f5d3a7d53f12bac4a68c7911b12f6382ef4fca",
"isometric-geometry-view-light": "c4c005fa55f64280abf9fccccdf7dc8efc3497bd626572bc11f45cc3cddaa159",
"split-corner-wall-before-dark": "3176dc67f54d5309f87c94e1077b4f69eb1db9f660469fbf97953038323430f3",
"split-corner-wall-thin-dark": "6da64905a3a4f8e4b4d457e5b20d2d55e0e7c2c601316a088c4bcc6557d35cc6",
"split-corner-wall-thick-dark": "494d559aa71ee85f90b8cfa11c1d3087fa123e963dec2b0975e7ce6c1520852a",
"isometric-geometry-view-dark": "73939fa61bebe2254591a788fad455a026a5e3b7394eeff27d4fe9a905de7ad2",
"isometric-geometry-view-light": "fc62f6ee8935d2e0c57b4918bf5fe554056a5956747b53a752ae265349ce662e",
"isometric-live-layers-dark": "869c62bf9cd762c36d342d1a3bbd992969425b46b132f3243439735e2c75c14f",
"isometric-no-borders-dark": "36f972f95704bff81ea1a59bdf3cd2cf7b636ec7871780460e98b23e3ebc3da2",
"isometric-touch-kiosk-dark": "36c83bbe39809f346ebb5a0f4ed633c938e2fed028defaa48097c281db8e23a3",
"isometric-touch-kiosk-dark": "5eba7794e563e1ef5b9387184693c819976454d0efd222bd12b38aad8ea03e20",
"isometric-large-warm-remount-dark": "798d312671dffebf59034a39f2865a65ece27a84b150e77bc59038bb2567d07c",
"geometry-view-dark-fit": "a538deed6141b98b3e396d7da1024b18aee345312edd7954ad458751f08f48a3",
"geometry-view-light-fit": "0c57dee930f30a1f9c16e4e704675156f57a7623a4edf839c14a4fecbf12881c",
"geometry-plan-editor-dark": "6b06213324c5ff50c7176451307a33d8ac31ee636c96698f8efe61e8db763bae",
"opening-placement-door-thick-wall-dark": "24f472818f2246eeaa6568648ca4a6c42d9d84723fbcdd879435253399e2995d",
"geometry-devices-editor-dark": "c850e83f1af747f063b895109fe26d6a5804356fab505b0976073c28ad146104",
"geometry-decor-editor-dark": "44a95fd0b397c2729fea65c750fedcb11df10042ae198f3690151ead077c84e1",
"tray-wide-selection-en": "468ac6acfd7fcdcbfa947e032843ff117a32ae01b4ec30937a6cddecff534504",
"tray-wide-tool-ru": "388e03d7bf7a2391d0581e8446d0692048b9792e80bf3b9dd9bca86222fe9418",
"tray-medium-group-en": "190d5c356461ad2aa8b63b132416e8958a285047f8dea92301213678c7ac5a92",
"tray-medium-selection-ru": "ab91e88583b2863305434ce2775e31328638414671ff0a4f8ee82c8274c04405",
"tray-narrow-palette-en": "fb63483e7101d457d7fb1c83ae50435410ed797771adcbfde14dc2798a000ce1",
"tray-narrow-tool-ru": "60ce3c75de88b72fdb5d3fe7a16790184a8265c74f0f97e5be4fd83dbe0259fd",
"geometry-diagonal-45-opening-dark": "3736a75163d47a71f60c45cd554d604f27cfbc81119b5b55e8134da61a2a0f0a",
"geometry-view-dark-fit": "3df272f6c3c3d20e9e375ea037f3dbb885657b94b0b29d0067505f4a73741237",
"geometry-view-light-fit": "a7f2c9667d9872dd84a37d5318fd238c9eabcc0f413017eb19e02204587b4e1a",
"geometry-plan-editor-dark": "19b5c84943b70074ba1aae51d1e54f9a59c44dde34fff89e32f0babb1e90a2ca",
"opening-placement-door-thick-wall-dark": "395c03bbf5d968e83664fd6621f0ac25902e718022f2e92ffbcddb8ce629cf9c",
"geometry-devices-editor-dark": "a9e4846ce5453400b87e6ad3d575882bc23a07b59dc3eecb612ed872b0c871ec",
"geometry-decor-editor-dark": "435b36096bbb2996d56ff0af262ddebff4a727edd841b0fad9b8d4f507b987ac",
"tray-wide-selection-en": "024ac666ac4dac01a36f7d1fdbf3bb41f76a627377f7c02dc373f429dfe96d43",
"tray-wide-tool-ru": "669fc1cf04433b936c0e9d05799ad19eda8bc003d5ab71937d0512c8f49ba921",
"tray-medium-group-en": "50cd3980b21f554bec2e76f4d2e8032f4bd30c0679477eb2f5cbe22f68935b7c",
"tray-medium-selection-ru": "4e5f235be8ed6296e136641d172e727a6a6b7a9d061f0928a96ccc1103c19f1f",
"tray-narrow-palette-en": "88b9846e4b451ed95b7ae7d2c3183a2ea191d7668768992a1364c6a7a53eb0c6",
"tray-narrow-tool-ru": "c4130715b3cb31c68619dfc706a3aa308e86edf272bfae6833b20666764ece2b",
"geometry-diagonal-45-opening-dark": "01206d25631c8fd09fa077932fbb5c1ee115b65ba76b38e6f7b09315dbbf6002",
"openings-thick-wall-dark": "5aa0b3d26894bef9ab9fca25c31bbef2f13f2c410f5f6d3f61c8d608ceb929f8",
"openings-filled-tunnel-dark": "167d92c11e6a8b3ff0f31177ac5905f8db4b5fb03ee78b4965c40bc45aeee50f",
"openings-hidden-view-dark": "c85cc04d1d8622b98215e2bb83f5bb233a7cfb0ac684c912475ef7bc44245897",
@@ -42,15 +45,15 @@
"lighting-temp-glow-room-override-dark": "0a35d3508526187ea18e44456cfb8cd9e578a1864e896fad1c3eec2892c753e0",
"lighting-manual-auto-spill-overlap-dark": "6324dbe2079a255e7a194720c8c19f210549ac734e564270bc1373e6385b9cac",
"hover-over-glow-dark": "fc14ba6f6b670e61c0fb5be277e67551ea2da7a06b5c167a8c2c989f1de08910",
"hover-nested-room-dark": "b70e385829a4fe45a77f5e1c3f8af4e18efec5282fb9594b26fab08c1f113d52",
"hover-nested-room-dark": "6c09526ad885c4555063def5b43287b41124a81d90972c425084dba6e622d055",
"large-house-zoom-040-dark": "5f11c4b78318a64c2a7cf803716661eea506609d4f0a6bb3d64a709f8c49db1d",
"large-house-zoom-250-dark": "c906426f888ff4e306c5c334c6329b387fc5ca368e229f55acc33c202351a1ac",
"large-house-warm-remount-dark": "6baf4baed1c735c64dfe1e69d9864ca287ffc0e8452d00e801f0873e98b187ee",
"device-dialog-desktop-en": "d6fcc83aa1335df1041e2b1aa445b0019d1e3567f98ef8f47a889894051f2b62",
"device-dialog-mobile-ru": "8cb928853ddacb61882804c3d00ead31da31bc4559ee6a8e293ef6b55cd5a463",
"device-help-popover-light-ru": "f1bf21d62a5dd349aa57b746069c5aef58d7a26b0b9d9e0c233fde0c1d56d7eb",
"decor-color-popover-mobile-ru": "16d2859d1ed3715c1c4d3d451a8428c37e91ba00da17272c59cf83420a7b6c31",
"backup-full-preview-desktop-en": "7f12943d1027c89f6fe46978fa1f4e1bcdbf85a1216281d850d467e68f52cbda",
"decor-color-popover-mobile-ru": "46d4c2e4dd20c3a38e90efe3db59b3e878bdbcf273fbc1aa23de4b230723fa6e",
"backup-full-preview-desktop-en": "1957c1c797c7793fb8dcf592ca74c9f2e3eccfcf4bac1d0187482c055bebb93b",
"backup-space-preview-mobile-ru": "998c6b52c1cc95109feb440a9966cade738154ceeae387246d40f8c56f9e8a3a"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 KiB

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 KiB

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 KiB

After

Width:  |  Height:  |  Size: 320 KiB

File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+16
View File
@@ -2,11 +2,27 @@
## Unreleased
## v1.63.0 — 2026-08-13
- Preserved explicit door, window and gate bindings when their standalone
sensor or lock marker is removed, and fixed the supported empty state after
deleting the last space
([#104](https://github.com/Matysh/houseplan-card/issues/104),
[#111](https://github.com/Matysh/houseplan-card/issues/111)).
- Splitting a room from an existing corner no longer deforms the exterior
facade, including with thick dividers. Flat, static and hidden isometric
rendering use the same preserved wall geometry
([#123](https://github.com/Matysh/houseplan-card/issues/123)).
- Small fixes and improvements.
## v1.63.0-beta.2 — 2026-08-13
- Splitting a room from an existing corner no longer deforms the exterior wall
or pulls a thick internal divider through the facade. Plan, View, kiosk,
static cards, hidden isometric rendering and light obstacles now use the same
preserved exterior geometry, including already saved plans
([#123](https://github.com/Matysh/houseplan-card/issues/123)).
- Small fixes and improvements.
## v1.63.0-beta.1 — 2026-08-13
+16
View File
@@ -8,11 +8,27 @@
## Unreleased
## v1.63.0 — 2026-08-13
- Сохранены явные привязки дверей, окон и ворот после удаления отдельного
маркера датчика или замка; исправлено предусмотренное пустое состояние после
удаления последнего пространства
([#104](https://github.com/Matysh/houseplan-card/issues/104),
[#111](https://github.com/Matysh/houseplan-card/issues/111)).
- Split из существующего угла комнаты больше не деформирует наружный фасад,
в том числе с толстым разделителем. Плоский, статичный и скрытый
изометрический рендер используют одну сохранённую геометрию стен
([#123](https://github.com/Matysh/houseplan-card/issues/123)).
- Мелкие исправления и улучшения.
## v1.63.0-beta.2 — 2026-08-13
- Split из существующего угла комнаты больше не деформирует наружную стену и
не вытягивает толстый внутренний разделитель сквозь фасад. Редактор плана,
View, киоск, статичная карточка, скрытая изометрия и световые препятствия
используют одну сохранённую наружную геометрию, в том числе для уже
сохранённых планов ([#123](https://github.com/Matysh/houseplan-card/issues/123)).
- Мелкие исправления и улучшения.
## v1.63.0-beta.1 — 2026-08-13
+9 -7
View File
@@ -1,16 +1,18 @@
<!-- release: v1.63.0-beta.1 -->
<!-- release: v1.63.0 -->
## Основное
- Удаление маркера датчика или замка больше не разрывает его привязку к двери, окну или воротам.
- Исправлено падение после удаления последнего пространства; из пустого состояния снова можно добавить пространство.
- Удаление отдельного маркера больше не разрывает явную привязку двери, окна или ворот.
- После удаления последнего пространства интеграция остаётся рабочей и позволяет создать новое.
- Разделение комнаты из существующего угла больше не искажает наружный фасад даже при толстом разделителе.
- Мелкие исправления и улучшения.
## Highlights
- Deleting a sensor or lock marker no longer breaks its door, window or gate binding.
- Fixed the crash after deleting the last space; a space can again be added from the empty state.
- Removing a standalone marker no longer breaks an explicit door, window or gate binding.
- The integration remains usable after deleting the last space and allows a new one to be created.
- Splitting a room from an existing corner no longer deforms the exterior facade, even with a thick divider.
- Small fixes and improvements.
[Полный список изменений на русском](https://github.com/Matysh/houseplan-card/blob/v1.63.0-beta.1/docs/CHANGELOG.ru.md)
· [Full changelog in English](https://github.com/Matysh/houseplan-card/blob/v1.63.0-beta.1/docs/CHANGELOG.md)
[Полный список изменений на русском](https://github.com/Matysh/houseplan-card/blob/v1.63.0/docs/CHANGELOG.ru.md)
· [Full changelog in English](https://github.com/Matysh/houseplan-card/blob/v1.63.0/docs/CHANGELOG.md)
+2 -2
View File
@@ -21,8 +21,8 @@ metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
| Item | State |
|---|---|
| Version | **v1.63.0-beta.1** everywhere (manifest, const.py, package.json, CARD_VERSION) — prerelease candidate |
| Current local cycle | v1.63.0-beta.1 fixes the empty-plan crash (#111) and preserves explicit opening sensor/lock references after standalone marker deletion (#104). Development after that beta preserves the exterior facade when Split starts or ends at a room corner (#123), using one wall geometry for flat/static/isometric rendering and light. The line also carries the reviewed process automation work #105 and #118–#121. |
| Version | **v1.63.0** everywhere (manifest, const.py, package.json, CARD_VERSION) — stable promotion candidate after published v1.63.0-beta.2 |
| Current local cycle | v1.63.0 promotes the published beta line without product-code changes. v1.63.0-beta.2 preserves the exterior facade when Split starts or ends at a room corner (#123), using one wall geometry for flat/static/isometric rendering and light; v1.63.0-beta.1 contains the empty-plan and opening-reference fixes (#111, #104), plus reviewed process automation #105 and #118–#121. |
| Hidden Labs Stage | #89 Stage 1 ships in v1.63.0-beta.1 as a hidden, expiring `iso` Labs experiment: a fixed near-top orthographic volumetric View. Flat remains default; editors and `houseplan-space-card` remain flat; all existing floor live effects and HA actions are preserved. This is internal, not a public feature. |
| Workflow | Owner's rule since 2026-08-07: ordinary fixes/features are made **locally, without tests and without commits**. A requested pre-release gets a production build plus the smallest targeted unit/smoke set covering the changed surfaces, one tested `dev` commit/tag and a GitHub Release with `prerelease=true`; `main` stays untouched. The complete local frontend/backend/smoke gate runs only before a stable release, after which `main` is fast-forwarded to the exact tested `dev` SHA and the GitHub Release uses `prerelease=false`. Release bodies are short and bilingual (Russian first): only significant user changes get individual bullets, while minor/code-only work is grouped as `Мелкие исправления и улучшения` / `Small fixes and improvements`; every body ends with separate links to the Russian and English changelogs. Detailed RU/EN changelog bullets may link the corresponding closed GitHub Issues; open or partially delivered issues are never presented as shipped. Telegram announcements are sent only for stable releases; beta and RC publication is silent. `docs/RELEASE-NOTES.md` is the current canonical body instance; `npm run release:prerelease -- <tag> --issues=… --yes` is the primary local publication path and the manual `Publish prerelease` workflow is its GitHub-only equivalent once present on `main`. Nothing is copied to the home instance by hand |
| GitHub | https://github.com/Matysh/houseplan-card — [Issues](https://github.com/Matysh/houseplan-card/issues) are the canonical task records and the linked [Project v2](https://github.com/users/Matysh/projects/1) is the canonical priority/status view; both must stay current. `main` carries stable releases; pre-release tags may point directly at `dev`. Work lands on `dev` and is merged into `main` for a stable release, so `dev` is normally equal to or ahead of `main`, never behind. Push via SSH key `ha_jb` (remote git@github.com:…); API releases via the fine-grained PAT in `~/.git-credentials` (Contents R/W, issued 2026-07-23) |
-155
View File
@@ -1,155 +0,0 @@
# CODE-REVIEW-123-r2
- **Issue:** https://github.com/Matysh/houseplan-card/issues/123
- **ТЗ:** `docs/specs/123-corner-split-wall.md` (зелёное ревью
`docs/reviews/SPEC-REVIEW-123-r1.md`)
- **Диапазон:** `git log --oneline origin/dev..HEAD` — 6 коммитов; относительно
предыдущего цикла (`docs/reviews/CODE-REVIEW-123-r1.md`, снят на коммите
`024a1ac`) диапазон вырос ровно на один коммит:
`e79f8f5 Fix corner split smoke geometry input` (`Issue: #123`,
`User-Visible: no`). `git diff 024a1ac..HEAD --stat` подтверждает: изменён
только `demo/smoke_split_corner_wall.mjs` (+6/−1 строк), продуктовый код
(`src/wall-thickness.ts`, `src/houseplan-card.ts`, `src/space-render.ts`) не
тронут ни байтом.
- **Роль:** ревьюер кода (не исполнитель), этап `S7-code-review`
- **Цикл:** r2/4
## Скоуп ревью
Единственная блокирующая находка r1 (`High-1`) — сломанная сигнатура вызова
`_lightBarriers(c._spaceModel())` в `demo/smoke_split_corner_wall.mjs:87`,
из-за которой смок падал необработанным исключением до выполнения хотя бы
одной проверки, и AC7 (`unit + smoke`)/AC8 (`smoke + golden`)/AC9
(`unit + smoke`) не были подтверждены доставленным доказательством. Скоуп
этого цикла: (1) убедиться, что фикс `e79f8f5` действительно чинит вызов, а не
маскирует падение; (2) прогнать смок и убедиться, что все 14 полей — `true`;
(3) убедиться, что тест по-прежнему умеет **содержательно** падать, а не
превратился в тавтологию; (4) поскольку продуктовый код не менялся с r1,
повторно прогнать быстрые гейты и точечные смоки по затронутым поверхностям
для очистки от сомнений, не переделывая заново детальное чтение
`src/wall-thickness.ts`, уже выполненное в r1 (не изменилось — см. diff-статы
выше).
## Как проверялось
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | `npx tsc --noEmit` | зелёный, без вывода |
| Unit | `npm test` | `752/752` (`npm run inventory` подтверждает то же число), 0 fail |
| Build | `npm run build` | зелёный |
| Bundle sync | `cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js` и `cmp dist/houseplan-card.js demo/srv/assets/houseplan-card.js` | оба совпадают побайтно; sha256 всех трёх `182fb55a…483ff` — идентичен значению из r1 (ожидаемо: продуктовый код не менялся) |
| Process gate | `node scripts/process-gate.mjs` | `диапазон origin/dev..HEAD, коммитов 6`, `гейт пройден, предупреждений 0` |
| Process gate + issues | `node scripts/process-gate.mjs --issues` | `гейт пройден, предупреждений 0` (метка issue #123 подтверждена через `gh`: ровно одна `S*` — `S7-code-review`) |
| Целевой browser smoke | `node demo/smoke_split_corner_wall.mjs` (после `npm run build`, синхронизация `demo/srv/assets/houseplan-card.js`) | **`OK`**, все 14 полей `true`: `beforeDrawn`, `wall0/15/100KeepsFacade`, `paper0/15/100KeepsFacade`, `dividerChangesInterior`, `lightUsesFacade`, `planViewParity`, `kioskParity`, `isoUsesCanonicalBody`, `staticParity`, `renderDoesNotRewriteConfig` |
| Regression-can-fail (сам доставленный смок, не независимая копия) | доставленный `demo/smoke_split_corner_wall.mjs` (версия из `e79f8f5`) скопирован в чистый `git worktree` на `52ec0fb` (коммит непосредственно перед продуктовым фиксом `47c6f10`, т.е. добаговый `wallBodiesGeometry`), пересобран и прогнан там | `FAILED (7)`: `wall0/15/100KeepsFacade`, `paper0/15/100KeepsFacade`, `lightUsesFacade` — все `expected true, got false`, `planViewParity`/`kioskParity`/`isoUsesCanonicalBody`/`staticParity`/`renderDoesNotRewriteConfig` остаются `true` (паритет между поверхностями держится даже на баге — расходится именно ожидаемый факт «фасад сохранён»). Падение содержательное (конкретные `false`, не исключение), т.е. смок доказывает именно то, что называет AC, а не тавтологию |
| Точечные browser smokes по затронутым поверхностям (split/wall-thickness/glow/iso/static-card) | `node demo/smoke_wall_thickness.mjs`, `smoke_merge_split.mjs`, `smoke_split_nonsnap.mjs`, `smoke_split_polyline.mjs`, `smoke_glow.mjs`, `smoke_isometric_contract.mjs`, `smoke_space_card.mjs` | все `OK`, регрессий на смежных поверхностях нет |
| Golden/performance/backend | не запускались | пре-релизные гейты по `PROCESS.md` §8/§11.4; визуальный/перф/backend-код не менялся с r1 (см. diff-статы), решение о непрогоне уже обосновано в r1 и остаётся в силе |
Полный набор из 128 browser-смоков не прогонялся — правка этого цикла точечная
(один файл демо-гарнеса), затронутые поверхности перечислены выше и покрыты.
## Находки
Блокирующих (High/Medium) находок нет. High-1 из r1 закрыт.
### Low-1 — смок строит `lightPolys` не буквально через хелпер `roomPoly(r)`
**Файл:** `demo/smoke_split_corner_wall.mjs:88-90`
```js
const lightPolys = lightSpace.rooms
.filter((room) => Array.isArray(room.poly))
.map((room) => ({ r: room, poly: room.poly }));
```
Продуктовый `_renderGlowLayer` (`src/houseplan-card.ts:13250-13252`) строит тот
же список через `roomPoly(r)` (`src/logic.ts:103-108`), которая (а) достаёт
`r.poly`, только если в нём **не менее 3** точек, и (б) для комнаты без
явного `poly` вычисляет прямоугольник из `x/y/w/h`. Смок вместо этого
фильтрует `Array.isArray(room.poly)` без проверки длины и не имеет пути для
`x/y/w/h`-комнат.
Для фикстуры issue (все комнаты заданы явным `poly` длиной 3 или 4)
результат совпадает с продуктовым один в один — расхождение не проявляется,
и AC7 доказан корректно для того сценария, который называет ТЗ. Но если этот
файл когда-нибудь расширят на комнату без явного `poly` (`x/y/w/h`), копия
молча исключит такую комнату из `lightPolys` там, где продукт бы её включил
— тихое расхождение, а не падение с сообщением.
**Решение ревьюера:** Low, не блокирует зелёный вердикт — фактическое
поведение для покрываемого сценария корректно, откладываю на усмотрение
автора при следующей правке этого файла (например, заменить построение на
прямой вызов `roomPoly` из продукта, если он становится доступен смоку).
## Что проверено и корректно
- **High-1 (r1) закрыт:** `_lightBarriers(lightSpace, lightPolys, lightPhysical)`
теперь вызывается с тем же числом и порядком аргументов, что и
`_renderGlowLayer` (`polys`, `physical` строятся явно, `physical` — через
тот же `c._physicalBodiesR(lightSpace)`, что и в продукте). Смок выполняется
до конца, `checkAll`/`finish` печатают `OK`, все 14 полей — `true`.
- **AC7 (`unit + smoke`):** `lightUsesFacade: true` — Glow использует то же
исправленное препятствие (`masonryGeometry` из `_lightBarriers`), что и
рендер стен; подтверждено смоком и независимо не расходится с unit-уровнем
r1 (`src/wall-thickness.ts` не менялся).
- **AC8 (`smoke + golden`):** `planViewParity`, `kioskParity`,
`isoUsesCanonicalBody`, `staticParity` — все `true`; Plan, View/kiosk,
скрытая изометрия и `houseplan-space-card` рисуют идентичный путь `d` для
сценария из issue. Golden-эталоны (второй тип доказательства AC8) —
пре-релизный гейт, не запускался, консистентно с r1/§11.4 PROCESS.md.
- **AC9 (`unit + smoke`):** `renderDoesNotRewriteConfig: true` — рендер не
мутирует сохранённые `rooms`/`walls`; сравнение JSON до/после рендера
совпадает.
- **Дисциплина «тест умеет падать» — усилена относительно r1.** В r1 AC7–AC9
были подтверждены независимой копией сценария вне репозитория (сам
доставленный файл падал необработанным исключением). В этом цикле
содержательное падение показано на **самом доставленном** файле — прогон в
чистом worktree на добаговом коде (`52ec0fb`, до `47c6f10`) даёт `FAILED (7)`
с конкретными `expected/got`, не крах. Это закрывает главное сомнение r1:
теперь именно тот файл, что лежит в репозитории, доказывает регресс, а не
только рассуждение ревьюера о нём.
- **Продуктовый код не менялся с r1:** `git diff 024a1ac..HEAD --stat`
показывает изменения только в `demo/smoke_split_corner_wall.mjs`. Всё, что
r1 проверил чтением и тестами по AC1–AC6, AC9 (unit-часть), AC10, AC11
(кэширование), AC12, AC13 (документация/changelog), остаётся в силе без
повторного разбора — предмет разбора не менялся, и разбор `r1` уже прошёл
свой цикл ревью.
- **Трейлеры и процесс:** `node scripts/process-gate.mjs` /
`--issues` — зелёные без предупреждений; коммит `e79f8f5` несёт
`Issue: #123` и `User-Visible: no` — корректно, это правка тестового
гарнеса (`demo/**`, класс B), поведение продукта не меняет, изменений в
changelog не требует и их нет. Метка issue — ровно одна, `S7-code-review`.
`origin/dev` не сдвинулся с момента слияния в ветку задачи (`merge-base`
совпадает с текущим `origin/dev`), ребейз перед мержем не потребуется.
- **Точечные смоки по затронутым поверхностям** (`smoke_wall_thickness`,
`smoke_merge_split`, `smoke_split_nonsnap`, `smoke_split_polyline`,
`smoke_glow`, `smoke_isometric_contract`, `smoke_space_card`) — все `OK`,
регрессий не найдено.
## Чего не проверял
- Полный набор из 128 browser-смоков — правка точечная (один файл демо-
гарнеса), полный прогон не пропорционален объёму изменения; прогнаны
целевой смок AC7–AC9 плюс смоки по затронутым поверхностям (см. таблицу).
- `npm run golden:verify` и `performance_smoke` — пре-релизные гейты
(`PROCESS.md` §8/§11.4), визуальный рендер и перф-чувствительные пути не
менялись с r1; будущий провал чинится по §11.4 без нового код-ревью.
- `tests_backend` — `custom_components/houseplan/**/*.py` не входит в
диапазон.
- Повторное детальное чтение `src/wall-thickness.ts`/`src/houseplan-card.ts`/
`src/space-render.ts` построчно — не требовалось: файлы не изменились со
времени r1, где это чтение уже выполнено и задокументировано.
- Low-1 не проверялся на альтернативной фикстуре (комната без явного `poly`)
— вне сценария, который называет ТЗ; см. решение ревьюера в находке.
## Вердикт
Зелёный. High: 0, Medium: 0 (Low: 1, не блокирует, решение зафиксировано в
находке Low-1 выше — оставлено на усмотрение автора без нового цикла).
Единственная блокирующая находка r1 устранена: доставленный
`demo/smoke_split_corner_wall.mjs` теперь вызывает `_lightBarriers` с полной
сигнатурой, проходит до конца с `OK` по всем 14 полям и содержательно падает
на добаговом коде того же файла (не независимой копии) — AC7, AC8, AC9
подтверждены доказательством, которое называет ТЗ. Продуктовый код не менялся
с r1 и остаётся подтверждённым: 752/752 unit, три bundle-снимка побайтно
идентичны, трейлеры и процесс-гейт зелёные.
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "houseplan-card",
"version": "1.63.0-beta.1",
"version": "1.63.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "houseplan-card",
"version": "1.63.0-beta.1",
"version": "1.63.0",
"license": "MIT",
"dependencies": {
"lit": "^3.1.3",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "houseplan-card",
"version": "1.63.0-beta.1",
"version": "1.63.0",
"description": "Interactive house plan Lovelace card for Home Assistant",
"license": "MIT",
"type": "module",
+43 -4
View File
@@ -67,6 +67,12 @@ const CLASS_C = [
const CHANGELOGS = ['docs/CHANGELOG.md', 'docs/CHANGELOG.ru.md'];
// Метки, при которых файла ТЗ в docs/specs/ быть не должно: на лёгком треке ТЗ
// живёт в теле issue (§5), на коротком — там же, и ревью ТЗ вообще не проводится
// (§5.1, issue #128). Офлайн эти случаи неотличимы от «ТЗ не написано», поэтому
// проверка 3 краснеет только когда метки прочитаны.
export const NO_SPEC_FILE = ['small', 'trivial'];
export const ALLOWED_STATUS = ['S5-ready', 'S6-in-progress', 'S7-code-review', 'S8-merged'];
export const STRICT_STATUS = ['S5-ready', 'S6-in-progress', 'S7-code-review'];
@@ -235,12 +241,12 @@ export function checkSpecs(commits, specFiles, labelsOf = null) {
if (labels === null) {
out.push({
level: 'warn', rule: 3, sha: c.short,
msg: `класс A по ${t}, но ТЗ docs/specs/${nn}-*.md не найдено — допустимо только при метке small`,
msg: `класс A по ${t}, но ТЗ docs/specs/${nn}-*.md не найдено — допустимо при метке small или trivial`,
});
} else if (!labels.includes('small')) {
} else if (!labels.some((l) => NO_SPEC_FILE.includes(l))) {
out.push({
level: 'fail', rule: 3, sha: c.short,
msg: `класс A по ${t}: ТЗ docs/specs/${nn}-*.md нет, и метки small на issue нет — код без ТЗ`,
msg: `класс A по ${t}: ТЗ docs/specs/${nn}-*.md нет, и метки ${NO_SPEC_FILE.join(' / ')} на issue нет — код без ТЗ`,
});
}
}
@@ -283,6 +289,22 @@ export function commitsUnderRuleOne(commits) {
);
}
export function isStableTarget(targetRef) {
return /^(?:refs\/heads\/)?main$/.test(targetRef ?? '');
}
// При stable promotion диапазон main..candidate закономерно содержит коммиты,
// уже выпущенные prerelease-тегом. Их issue к этому моменту обязаны быть закрыты
// (§2.8), поэтому повторная online-проверка статуса дала бы ложный отказ. Новые
// post-beta коммиты остаются в выборке и проверяются fail-closed как обычно.
export function commitsNeedingIssueStatus(
commits, { targetRef = '', isPublishedPrereleaseCommit = () => false } = {},
) {
const underRuleOne = commitsUnderRuleOne(commits);
if (!isStableTarget(targetRef)) return underRuleOne;
return underRuleOne.filter((commit) => !isPublishedPrereleaseCommit(commit.sha));
}
// 8. статус issue. Fail closed: недоступный или закрытый issue — отказ, а не
// пропуск. Гейт, который молчит при недоступном источнике правды, бесполезен.
export function checkIssueStatuses(numbers, runner, { allowed = ALLOWED_STATUS } = {}) {
@@ -376,6 +398,7 @@ function main(argv) {
const repo = value('repo', process.cwd());
const allowed = flag('no-merged') ? STRICT_STATUS : ALLOWED_STATUS;
const targetRef = value('target-ref', process.env.TARGET_REF ?? '');
let range = value('range');
if (!range && flag('github-range')) {
@@ -422,7 +445,23 @@ function main(argv) {
// проверки 3. Второй запрос по тому же issue — лишний сетевой вызов.
let labelsOf = null;
if (flag('issues')) {
const numbers = [...new Set(commitsUnderRuleOne(commits).flatMap((c) => c.issues).map((t) => t.slice(1)))];
const prereleaseTags = isStableTarget(targetRef)
? git(['tag', '--list'], repo).split('\n').map((s) => s.trim()).filter((tag) =>
/^v(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)-[0-9A-Za-z.-]+$/.test(tag))
: [];
const publishedCache = new Map();
const isPublishedPrereleaseCommit = (sha) => {
if (!publishedCache.has(sha)) {
publishedCache.set(sha, prereleaseTags.some((tag) =>
spawnSync('git', ['-C', repo, 'merge-base', '--is-ancestor', sha, `${tag}^{commit}`],
{ encoding: 'utf8' }).status === 0));
}
return publishedCache.get(sha);
};
const statusCommits = commitsNeedingIssueStatus(commits, {
targetRef, isPublishedPrereleaseCommit,
});
const numbers = [...new Set(statusCommits.flatMap((c) => c.issues).map((t) => t.slice(1)))];
const runner = ghRunner(process.env.HP_REPO ?? 'Matysh/houseplan-card', process.env.GH_BIN ?? 'gh');
const cache = new Map();
const cached = (nn) => {
+1 -1
View File
@@ -208,7 +208,7 @@ import {
} from './opening-placement';
import { safeStoredColor } from './color';
const CARD_VERSION = '1.63.0-beta.1';
const CARD_VERSION = '1.63.0';
const DISPLAY_LABEL_KEYS: Record<DeviceDisplayMode, I18nKey> = {
badge: 'display.badge',
icon_ripple: 'display.icon_ripple',
+33 -1
View File
@@ -14,6 +14,7 @@ import {
checkReviewDocLimit,
checkSpecs,
classify,
commitsNeedingIssueStatus,
commitsUnderRuleOne,
evaluateCommit,
makeCommit,
@@ -134,8 +135,9 @@ test('a class A commit without a spec warns offline and fails with labels', () =
assert.equal(offline[0].level, 'warn');
assert.equal(offline[0].rule, 3);
// С метками: small оправдывает отсутствие файла, его отсутствие — нет.
// С метками: small и trivial оправдывают отсутствие файла, их отсутствие — нет.
assert.deepEqual(checkSpecs([c], [], () => ['small', 'S5-ready']), []);
assert.deepEqual(checkSpecs([c], [], () => ['trivial', 'S5-ready']), []);
const strict = checkSpecs([c], [], () => ['S5-ready']);
assert.equal(strict.length, 1);
assert.equal(strict[0].level, 'fail');
@@ -169,6 +171,36 @@ test('only class A/B commits are held to the issue status', () => {
assert.deepEqual(commitsUnderRuleOne([reviewDoc]), []);
});
test('stable promotion skips status recheck only for commits already published in a prerelease', () => {
const published = makeCommit({
sha: 'a'.repeat(40), subject: 'Fix shipped in beta', body: 'Issue: #123', files: ['src/a.ts'],
});
const postBeta = makeCommit({
sha: 'b'.repeat(40), subject: 'New promotion work', body: 'Issue: #130', files: ['scripts/a.mjs'],
});
const publishedShas = new Set([published.sha]);
assert.deepEqual(
commitsNeedingIssueStatus([published, postBeta], {
targetRef: 'refs/heads/main',
isPublishedPrereleaseCommit: (sha) => publishedShas.has(sha),
}).map((commit) => commit.sha),
[postBeta.sha],
);
assert.deepEqual(
rules(checkIssueStatuses(['130'], () => ({
ok: true, json: { state: 'CLOSED', labels: [] },
}))),
[8],
);
assert.deepEqual(
commitsNeedingIssueStatus([published, postBeta], {
targetRef: 'refs/heads/dev',
isPublishedPrereleaseCommit: (sha) => publishedShas.has(sha),
}).map((commit) => commit.sha),
[published.sha, postBeta.sha],
);
});
test('issue status check is fail closed when the source of truth is unreachable', () => {
// AC3: недоступный gh должен давать отказ, а не молчаливый пропуск.
const broken = () => ({ ok: false, error: 'gh: could not resolve to a Repository' });