Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50099acc75 | ||
|
|
a282f850af | ||
|
|
d7f3bb8119 | ||
|
|
5c6ab8ea9b | ||
|
|
fda4893f0c | ||
|
|
888e90450a | ||
|
|
b2263a6551 | ||
|
|
565f518dcd | ||
|
|
02e0c9801d | ||
|
|
e93c405b13 | ||
|
|
955de3e69c | ||
|
|
7af4146614 | ||
|
|
a43602934c | ||
|
|
516257e322 | ||
|
|
9177c9a944 | ||
|
|
8a3f6efa0a | ||
|
|
be7d6b9706 | ||
|
|
2fd042a7de | ||
|
|
d7e2c4d4f0 | ||
|
|
d1be6891b2 | ||
|
|
9be81c1413 | ||
|
|
fafeca4540 | ||
|
|
3ade633538 | ||
|
|
68596a75a0 | ||
|
|
65a86db122 | ||
|
|
a29df12e0b | ||
|
|
9146b4c357 | ||
|
|
9ad01be813 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 291 KiB |
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 280 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 320 KiB After Width: | Height: | Size: 320 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 323 KiB After Width: | Height: | Size: 322 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 321 KiB After Width: | Height: | Size: 320 KiB |
|
Before Width: | Height: | Size: 321 KiB After Width: | Height: | Size: 320 KiB |
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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-снимка побайтно
|
||||
идентичны, трейлеры и процесс-гейт зелёные.
|
||||
@@ -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,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",
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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' });
|
||||
|
||||