Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
196be82d94 | ||
|
|
f340157f59 | ||
|
|
7acf5c74ad | ||
|
|
075a2d3fb1 | ||
|
|
7a4032ba26 | ||
|
|
9a7020ce42 | ||
|
|
19edda83e3 | ||
|
|
9fe7f88cb3 | ||
|
|
f1a87fbad9 | ||
|
|
ac8eac6034 | ||
|
|
bdac4b4fdc | ||
|
|
6fc789e4c8 | ||
|
|
0a066177cf | ||
|
|
85cba3d8b4 | ||
|
|
cbc39cea7b | ||
|
|
d24b0f0eac | ||
|
|
19587a797f | ||
|
|
1ffb1c4eb3 | ||
|
|
b11d60eb4e | ||
|
|
6183119f1e | ||
|
|
7871efb0d6 | ||
|
|
e62dec74cd | ||
|
|
2475403469 | ||
|
|
2aeda2dfa4 | ||
|
|
855bede537 | ||
|
|
fc237f93be | ||
|
|
15207dbacd | ||
|
|
2f857a0f6b | ||
|
|
7e8605981b | ||
|
|
47c245b5ba | ||
|
|
2728a0ee54 | ||
|
|
df2455c530 | ||
|
|
9bc23f9871 | ||
|
|
e45dcad47a | ||
|
|
944adc65a9 | ||
|
|
21555a5991 | ||
|
|
71c8a87e67 | ||
|
|
8b775247a7 | ||
|
|
c9cd6cbf1f | ||
|
|
d36f79f156 | ||
|
|
05dfcd1c8a | ||
|
|
098732512c | ||
|
|
88946f69db | ||
|
|
7464d3e16c | ||
|
|
f2e114d71d | ||
|
|
9755b200e7 | ||
|
|
8852207b18 | ||
|
|
8176f1190f | ||
|
|
d64338224d | ||
|
|
3f8aae838f | ||
|
|
59a60d526d | ||
|
|
ac6f4bfe2e | ||
|
|
93876d7569 | ||
|
|
ea1f19c352 | ||
|
|
bcc5481557 | ||
|
|
ce26a051e6 | ||
|
|
e18a7c3385 | ||
|
|
16ede0c027 | ||
|
|
42e7bc24a8 | ||
|
|
060d9ea547 | ||
|
|
efdc9df0c6 | ||
|
|
ce4f820a03 | ||
|
|
b68aa216a9 | ||
|
|
19d2463dcc | ||
|
|
8aa7b9295e | ||
|
|
cf876c712a | ||
|
|
2d6f1e8be3 | ||
|
|
6903f3320b | ||
|
|
847ebc91a9 | ||
|
|
2fe20a095b | ||
|
|
8eb97bcf46 | ||
|
|
d03a68b88a | ||
|
|
781902b3a8 | ||
|
|
f8a12f26b3 | ||
|
|
2fdafb5bef | ||
|
|
d06cca2c19 | ||
|
|
8ffbdf8a28 | ||
|
|
c93b3a90ef | ||
|
|
72d49040bf | ||
|
|
288b9b41ab | ||
|
|
3bdd3c5163 | ||
|
|
42f437cafe | ||
|
|
942b9ede67 | ||
|
|
e53f9d4f77 | ||
|
|
40b73cc599 | ||
|
|
6cdcb1ea47 | ||
|
|
85119fd9a2 | ||
|
|
f11c2a4c5d | ||
|
|
bae6378006 | ||
|
|
467eaf873a | ||
|
|
698f638969 | ||
|
|
3e48002c92 | ||
|
|
4c64189564 | ||
|
|
9a19e17858 | ||
|
|
dc83a875f4 | ||
|
|
5321c32ddc | ||
|
|
8906675879 | ||
|
|
d3c88f06f9 | ||
|
|
cff83e43ec | ||
|
|
422221fe93 | ||
|
|
27517db8a4 | ||
|
|
1ecf96d596 | ||
|
|
d65b9e39d5 | ||
|
|
563fbe0759 | ||
|
|
b856dd33c8 | ||
|
|
78d4da4abe | ||
|
|
88c4e4e9ae | ||
|
|
4b8a82c08a | ||
|
|
54f197209d | ||
|
|
43fab645b0 | ||
|
|
9287f798b3 | ||
|
|
f5c6d70b7c | ||
|
|
6d3738adef | ||
|
|
6297af6701 | ||
|
|
803c0f0eaa | ||
|
|
e73e56dd94 | ||
|
|
7cc5423efe | ||
|
|
c40266431c | ||
|
|
4ab7ecb4a2 | ||
|
|
c6937f9fc7 | ||
|
|
9d4f68a4ee | ||
|
|
ec2b8c1522 | ||
|
|
8703727951 | ||
|
|
cc04d341d4 | ||
|
|
8501d10988 | ||
|
|
142340ee1c | ||
|
|
0e8e87303e | ||
|
|
106fdc096d | ||
|
|
e1f1c7d908 | ||
|
|
2ed050311b | ||
|
|
e5ab4659f6 |
@@ -52,7 +52,8 @@ jobs:
|
||||
# commit/tree, а не самостоятельно читают dev в разное время.
|
||||
material:
|
||||
name: "Зафиксировать неизменяемый материал"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
sha: ${{ steps.identity.outputs.sha }}
|
||||
tree: ${{ steps.identity.outputs.tree }}
|
||||
@@ -112,7 +113,7 @@ jobs:
|
||||
name: "Мутанты: каждый обязан красить тесты (шард ${{ matrix.shard }} из 6)"
|
||||
needs: material
|
||||
if: needs.material.outputs.reuse != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -208,7 +209,8 @@ jobs:
|
||||
name: "Доказать единый material всех шардов"
|
||||
needs: [material, mutants]
|
||||
if: always() && needs.material.outputs.reuse != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
@@ -239,7 +241,8 @@ jobs:
|
||||
name: "Записать маркер зелёного прогона"
|
||||
needs: [material, mutants, evidence]
|
||||
if: needs.material.outputs.reuse != 'true' && needs.mutants.result == 'success' && needs.evidence.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
@@ -275,7 +278,8 @@ jobs:
|
||||
# является — её шарды пропущены намеренно. Любой другой пропуск шардов
|
||||
# (упал material) по-прежнему заводит issue.
|
||||
if: always() && github.event_name == 'schedule' && needs.material.outputs.reuse != 'true' && (needs.mutants.result != 'success' || needs.evidence.result != 'success')
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
@@ -32,9 +32,29 @@ permissions:
|
||||
jobs:
|
||||
dispatch:
|
||||
name: "Запустить Validate на dev с полным набором и дождаться результата"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
# #658: при плане 02:30 UTC ночь фактически стартовала в 07:42–08:05 и
|
||||
# кончалась в рабочее время владельца. Сдвиг старта больше часа — видимое
|
||||
# предупреждение, а не молча съеденная ночь. `github.event.schedule` —
|
||||
# строка cron вызывающего `nightly.yml`; NOW_EPOCH подставляет только тест.
|
||||
- name: "Сдвиг старта ночи против расписания"
|
||||
if: github.event_name == 'schedule'
|
||||
env:
|
||||
SCHEDULE: ${{ github.event.schedule }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
read -r minute hour _ <<< "$SCHEDULE"
|
||||
now=${NOW_EPOCH:-$(date -u +%s)}
|
||||
planned=$(date -u -d "@$now" +%F)
|
||||
planned=$(date -u -d "$planned $hour:$minute" +%s)
|
||||
[ "$planned" -le "$now" ] || planned=$((planned - 86400))
|
||||
lag=$(( (now - planned) / 60 ))
|
||||
echo "- Старт по расписанию \`$SCHEDULE\` (UTC): сдвиг $lag мин" >> "$GITHUB_STEP_SUMMARY"
|
||||
if [ "$lag" -gt 60 ]; then
|
||||
echo "::warning::ночной прогон стартовал на $lag мин позже расписания ($SCHEDULE UTC) — очередь расписаний GitHub (#658)"
|
||||
fi
|
||||
- env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
|
||||
@@ -25,7 +25,7 @@ permissions:
|
||||
jobs:
|
||||
metrics:
|
||||
name: "Снимок недели: issue, раунды, прогоны"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
# Код — из dev, как у reconcile: расписание читается из main, а исполняется
|
||||
|
||||
@@ -19,7 +19,7 @@ permissions:
|
||||
jobs:
|
||||
reconcile:
|
||||
name: "Один снимок S4/S7 без polling модели"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
concurrency:
|
||||
group: process-reconcile
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
resume:
|
||||
name: "Разбудить раунд, ждавший этот Validate"
|
||||
if: github.event.workflow_run.event == 'workflow_dispatch' && startsWith(github.event.workflow_run.head_branch, 'issue/')
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
concurrency:
|
||||
group: process-resume-${{ github.event.workflow_run.head_branch }}
|
||||
|
||||
@@ -48,7 +48,8 @@ jobs:
|
||||
# Только статусные метки этапов ревью запускают конвейер (#499). Остальные
|
||||
# события помечаются skipped и не занимают место в группе concurrency.
|
||||
if: github.event.label.name == 'S4-spec-review' || github.event.label.name == 'S7-code-review'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
concurrency:
|
||||
group: process-issue-${{ github.event.issue.number }}
|
||||
cancel-in-progress: false
|
||||
@@ -282,7 +283,7 @@ jobs:
|
||||
issues: write
|
||||
needs: guard
|
||||
if: needs.guard.outputs.stage != ''
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
concurrency:
|
||||
group: process-issue-${{ github.event.issue.number }}
|
||||
cancel-in-progress: false
|
||||
@@ -446,11 +447,12 @@ jobs:
|
||||
printf 'конфликтуют:\n%s\n' "${files:-(git не назвал файлы)}"
|
||||
exit 0
|
||||
fi
|
||||
# #635 r2: ребейз мог принести в docs/reviews документы других задач,
|
||||
# а закоммиченный INDEX.md — снимок каталога — их не знает. Свежесть
|
||||
# держит тест «индекс свеж» в Validate, поэтому индекс пересобирается
|
||||
# здесь же, коммитом конвейера (класс C), до фиксации материала.
|
||||
node scripts/reviews-index.mjs --dir=docs/reviews --commit-if-stale --issue="$NUM"
|
||||
# #657 (1б): индекс ревью в ветке задачи не пересобирается — ни здесь,
|
||||
# ни при публикации документа. Он пересобирается только коммитами,
|
||||
# которые идут прямо в dev: слиянием кандидата (merge-candidate.mjs)
|
||||
# и публикацией документа ревью ТЗ. Коммит индекса в ветке конфликтовал
|
||||
# с документами соседей по построению (#643) и возвращал зелёные
|
||||
# задачи на повторное ревью; гейт «индекс свеж» судит только dev.
|
||||
# --force-with-lease с явным ожидаемым значением обязателен: между
|
||||
# fetch и push автор мог запушить коммит, и слепой --force потерял бы
|
||||
# его молча. Расхождение lease — падение прогона, а не предупреждение:
|
||||
@@ -851,7 +853,7 @@ jobs:
|
||||
issues: write
|
||||
needs: [guard, prepare]
|
||||
if: needs.prepare.outputs.proceed == 'true' && needs.prepare.outputs.reuse != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
concurrency:
|
||||
group: process-issue-${{ github.event.issue.number }}
|
||||
cancel-in-progress: false
|
||||
@@ -933,7 +935,7 @@ jobs:
|
||||
- name: Установить Chromium
|
||||
if: steps.pw.outputs.cache-hit != 'true'
|
||||
# Без --with-deps: системные библиотеки Chromium предустановлены в
|
||||
# образе ubuntu-latest, а apt при промахе кэша съедал минуты из бюджета
|
||||
# образе раннера, а apt при промахе кэша съедал минуты из бюджета
|
||||
# ревью и подолгу перебирал недоступное azure-зеркало (#175). Если
|
||||
# библиотека когда-нибудь пропадёт из образа, Chromium не запустится с
|
||||
# внятной ошибкой — тогда флаг вернуть.
|
||||
@@ -1195,7 +1197,7 @@ jobs:
|
||||
issues: write
|
||||
needs: [guard, prepare, model_review]
|
||||
if: always() && needs.guard.outputs.stage != ''
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
concurrency:
|
||||
group: process-issue-${{ github.event.issue.number }}
|
||||
cancel-in-progress: false
|
||||
@@ -1375,9 +1377,11 @@ jobs:
|
||||
# забрал бы всё, что там окажется, а после reset там не должно быть
|
||||
# ничего постороннего — но полагаться на «не должно» здесь нельзя.
|
||||
git add -- "$doc" 2>/dev/null || true
|
||||
# #635: индекс ревью пересобирается тем же коммитом, что и документ —
|
||||
# иначе он устаревает на первом же раунде. Генерируемый файл, класс C.
|
||||
if [ -f "$doc" ]; then
|
||||
# #635/#657 (1б): индекс пересобирается тем же коммитом, что и
|
||||
# документ, только когда документ идёт прямо в dev (ревью ТЗ). В ветку
|
||||
# задачи — один документ: индекс там конфликтовал бы с документами
|
||||
# соседей в dev по построению; его пересоберёт слияние кандидата.
|
||||
if [ -f "$doc" ] && [ "$target" = "dev" ]; then
|
||||
node scripts/reviews-index.mjs --dir=docs/reviews
|
||||
git add -- docs/reviews/INDEX.md
|
||||
fi
|
||||
|
||||
@@ -43,7 +43,8 @@ jobs:
|
||||
telegram:
|
||||
name: Оповещение в Telegram (только стабильные)
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'workflow_call' && inputs.prerelease == false) }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out release notes for a reusable call
|
||||
if: ${{ inputs.reusable == true }}
|
||||
|
||||
@@ -31,7 +31,8 @@ permissions:
|
||||
jobs:
|
||||
capture:
|
||||
name: Съёмка и сверка скриншот-индекса
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
|
||||
@@ -16,9 +16,10 @@ on:
|
||||
required: false
|
||||
default: dev
|
||||
schedule:
|
||||
# Каждую ночь, 01:00 UTC (04:00 MSK) — после суток правок и до ночного
|
||||
# полного Validate (nightly.yml, 02:30 UTC), чтобы не делить раннеры (#513).
|
||||
- cron: '0 1 * * *'
|
||||
# Каждую ночь, 00:43 UTC (03:43 MSK) — после суток правок и до ночного
|
||||
# полного Validate (nightly.yml, 02:17 UTC), чтобы не делить раннеры (#513).
|
||||
# Минута не круглая: такие старты GitHub сдвигает меньше (#658).
|
||||
- cron: '43 0 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -9,8 +9,10 @@ name: Ночной полный прогон dev
|
||||
# держит копии равными.
|
||||
|
||||
on:
|
||||
# 02:17 UTC, не на круглой минуте: старты «ровно в час/полчаса» GitHub
|
||||
# откладывает на часы (факт — 07:42–08:05 при плане 02:30, #658).
|
||||
schedule:
|
||||
- cron: '30 2 * * *'
|
||||
- cron: '17 2 * * *'
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
|
||||
@@ -6,8 +6,12 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- ".github/workflows/**"
|
||||
- "docs/**"
|
||||
schedule:
|
||||
- cron: "0 4 * * 1"
|
||||
# Минута не круглая (#658): доходит до main вместе со стабильным релизом.
|
||||
- cron: "11 4 * * 1"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
comparison_ref:
|
||||
@@ -28,7 +32,7 @@ jobs:
|
||||
# Every profile keeps base and candidate sequential on one hosted runner.
|
||||
# Independent profile pairs run in parallel: cross-profile timing is never
|
||||
# compared, while serialising all profile pairs cannot fit the job timeout.
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
@@ -10,8 +10,9 @@ name: Метрики процесса
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Понедельник 05:00 UTC — после ночных прогонов, до рабочего дня.
|
||||
- cron: '0 5 * * 1'
|
||||
# Понедельник 05:23 UTC — после ночных прогонов, до рабочего дня; минута
|
||||
# не круглая, чтобы не стоять в очереди «ровных» расписаний (#658).
|
||||
- cron: '23 5 * * 1'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
days:
|
||||
|
||||
@@ -21,7 +21,9 @@ concurrency:
|
||||
jobs:
|
||||
gate:
|
||||
name: "Гейт: зелёная Проверка и релизный контракт"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
# Больше ожидания зелёного Validate в release-gate.mjs (до 60 мин) (#658).
|
||||
timeout-minutes: 75
|
||||
outputs:
|
||||
sha: ${{ steps.candidate.outputs.sha }}
|
||||
tag: ${{ steps.candidate.outputs.tag }}
|
||||
@@ -122,7 +124,8 @@ jobs:
|
||||
publish:
|
||||
name: Публикация тега и релиза
|
||||
needs: gate
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
outputs:
|
||||
url: ${{ steps.verify.outputs.url }}
|
||||
newly_published: ${{ steps.release.outputs.newly_published }}
|
||||
@@ -289,7 +292,8 @@ jobs:
|
||||
close-merged:
|
||||
name: Закрытие вошедших issue
|
||||
needs: [gate, publish]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
@@ -46,7 +46,7 @@ concurrency:
|
||||
jobs:
|
||||
prepare:
|
||||
name: "Ревью релиза: вход линии"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
name: "Ревью релиза: работа модели"
|
||||
needs: prepare
|
||||
if: needs.prepare.outputs.proceed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
# Недоверенная стадия. Прав на запись нет никаких: ни в репозиторий, ни в
|
||||
# issue. Документ публикует `publish`; находки в issue превращает владелец.
|
||||
@@ -258,7 +258,7 @@ jobs:
|
||||
publish:
|
||||
name: "Ревью релиза: документ в dev"
|
||||
needs: [prepare, model_review]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -307,7 +307,7 @@ jobs:
|
||||
"$TAG" "$CANDIDATE" "${BASE:-—}" "${ISSUES:-—}" "$RUN_URL"
|
||||
printf '```\n'
|
||||
} > "$DOC"
|
||||
node scripts/reviews-index.mjs --dir=docs/reviews
|
||||
node scripts/reviews-index.mjs --dir=docs/reviews --strict
|
||||
git add -- "$DOC" docs/reviews/INDEX.md
|
||||
git diff --cached --name-only | node scripts/review-doc-guard.mjs
|
||||
git -c user.name="claude[bot]" \
|
||||
|
||||
@@ -48,7 +48,8 @@ jobs:
|
||||
name: "Кандидат: точный SHA, режим и черновик"
|
||||
# Публикация беты руками — не наш случай: у бет свой staged-путь.
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || !github.event.release.prerelease }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
sha: ${{ steps.resolve.outputs.sha }}
|
||||
tag: ${{ steps.resolve.outputs.tag }}
|
||||
@@ -142,7 +143,7 @@ jobs:
|
||||
needs: candidate
|
||||
if: ${{ needs.candidate.outputs.prerelease != 'true' }}
|
||||
continue-on-error: true
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
actions: write
|
||||
@@ -164,7 +165,10 @@ jobs:
|
||||
gate:
|
||||
name: "Гейт: контракт, Validate, Full Performance и E2E на точном SHA"
|
||||
needs: candidate
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
# Больше суммы собственных ожиданий job: Validate и Full Performance ждутся
|
||||
# до 60 мин каждый (release-gate.mjs), E2E — до 45 (e2e-gate.mjs): 165 < 180 (#658).
|
||||
timeout-minutes: 180
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
@@ -234,7 +238,8 @@ jobs:
|
||||
stage:
|
||||
name: "Сборка: ассеты, SHA256SUMS и загрузка в черновик"
|
||||
needs: [candidate, gate]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
@@ -374,7 +379,8 @@ jobs:
|
||||
publish:
|
||||
name: "Публикация и сверка публичных байтов"
|
||||
needs: [candidate, gate, stage]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
url: ${{ steps.verify.outputs.url }}
|
||||
name: ${{ steps.verify.outputs.name }}
|
||||
@@ -474,7 +480,8 @@ jobs:
|
||||
# make that distribution failure impossible to miss in the release run.
|
||||
if: ${{ needs.candidate.outputs.prerelease == 'true' }}
|
||||
needs: [candidate, publish]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Verify the published tag is the prerelease HACS will discover
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
|
||||
@@ -50,7 +50,8 @@ jobs:
|
||||
# действовать — на уровне шагов, с той же гранулярностью в логе.
|
||||
preflight:
|
||||
name: "Предполёт: документация, провенанс, процесс"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
# `actions: read` — чтение списка прогонов Validate (#388), `issues: read` —
|
||||
# проверка 8 процессного гейта. Права перечислены явно, потому что job
|
||||
# обращается к API сверх содержимого репозитория.
|
||||
@@ -247,7 +248,8 @@ jobs:
|
||||
# там делает код-ревью, которое гоняет гейты само (#127).
|
||||
changes:
|
||||
name: Классификация изменённых файлов
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
# Чтение списка прогонов Validate — единственное, что этой job нужно сверх
|
||||
# содержимого репозитория (#387).
|
||||
permissions:
|
||||
@@ -406,7 +408,8 @@ jobs:
|
||||
# performance_smoke включает набор, иначе glow-only результат засчитался бы
|
||||
# прогону, которому нужен изометрический профиль.
|
||||
needs: changes
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
smoke: ${{ steps.probe.outputs.smoke }}
|
||||
golden: ${{ steps.probe.outputs.golden }}
|
||||
@@ -555,7 +558,8 @@ jobs:
|
||||
name: "HACS: валидация репозитория"
|
||||
needs: changes
|
||||
if: needs.changes.outputs.integration == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- name: HACS validation
|
||||
@@ -567,7 +571,8 @@ jobs:
|
||||
name: "Hassfest: манифест интеграции"
|
||||
needs: changes
|
||||
if: needs.changes.outputs.integration == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- name: Hassfest validation
|
||||
@@ -598,7 +603,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5, 6]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
@@ -727,7 +732,8 @@ jobs:
|
||||
name: "Фронтенд: типы, юниты, мутанты, синхрон бандла"
|
||||
needs: changes
|
||||
if: needs.changes.outputs.frontend == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
# Полная история без блобов (#342): гейт «новый код не добавляет any»
|
||||
# diff-aware, ему нужен диапазон, а содержимое старых ревизий — нет.
|
||||
@@ -780,14 +786,21 @@ jobs:
|
||||
node scripts/no-new-any.mjs --base "$base" --head "$HEAD_SHA" || status=1
|
||||
node scripts/no-new-private-writes.mjs --base "$base" --head "$HEAD_SHA" || status=1
|
||||
exit "$status"
|
||||
- name: Unit tests
|
||||
run: npm test
|
||||
- name: Build
|
||||
run: npm run build
|
||||
# Часть unit-контрактов проверяет размеры реально поставляемых графов в
|
||||
# dist/ (#438, #593, #627). В issue-ветке закоммиченный dist законно
|
||||
# отстаёт от исходников (#657), поэтому юниты обязаны читать свежую
|
||||
# сборку — тот же порядок закреплён локальным gate:small.
|
||||
- name: Unit tests
|
||||
run: npm test
|
||||
# Entry, manifest and every content-hashed chunk are one artifact (#337).
|
||||
# #657: the committed bundle changes only in a release candidate, so the
|
||||
# fresh build must equal it only there; elsewhere the committed copy
|
||||
# legitimately lags and only the fresh dist is verified.
|
||||
- name: Card bundle trees in sync
|
||||
run: |
|
||||
node scripts/bundle-tree.mjs dist custom_components/houseplan/frontend
|
||||
node scripts/bundle-policy.mjs --verify HEAD
|
||||
npm run bundle:budget
|
||||
# #624: `tsc --noUnusedLocals` чист вне порта/харнесса, шесть чисел
|
||||
# связности монолита не выше scripts/monolith-baseline.json. После
|
||||
@@ -819,12 +832,48 @@ jobs:
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
|
||||
# #657 (2б): бандл в дереве dev меняет только кандидат беты, поэтому стенд
|
||||
# dev.houseplan.tech берёт его не из дерева, а из этого артефакта: после
|
||||
# зелёного фронтенда на push в dev собранный `dist/` публикуется в служебную
|
||||
# ветку `dev-build` одним коммитом без истории (scripts/dev-build.mjs), хост
|
||||
# забирает его demo/stand/update-dev-bundle.sh. Опубликуется только голова:
|
||||
# если dev ушёл вперёд, пушит следующий прогон. Не вход доказательства:
|
||||
# стенд — не гейт, и сбой публикации не красит проверку кода.
|
||||
dev_build:
|
||||
name: "Бандл головы dev для стенда"
|
||||
needs: frontend
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/dev' && needs.frontend.result == 'success'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
continue-on-error: true
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 22
|
||||
# download-artifact распаковывает поверх checkout и не удаляет старые
|
||||
# content-hashed чанки закоммиченного release-бандла (#657). Свежий
|
||||
# артефакт должен быть единственным содержимым dist, иначе bundle-tree
|
||||
# справедливо видит оставшиеся файлы как orphan assets.
|
||||
- name: Очистить закоммиченный dist перед артефактом
|
||||
run: |
|
||||
node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"
|
||||
- name: Забрать собранный бандл
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: card-bundle
|
||||
path: dist
|
||||
- name: Опубликовать в dev-build
|
||||
run: node scripts/dev-build.mjs --sha "$GITHUB_SHA" --dist dist
|
||||
|
||||
smoke:
|
||||
name: Смоки в браузере (шард ${{ matrix.shard }} из 3)
|
||||
# Gated on `frontend` so a typecheck failure does not burn browser minutes.
|
||||
needs: [changes, frontend, reuse]
|
||||
if: needs.changes.outputs.heavy == 'true' && needs.reuse.outputs.smoke != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
# Смоки шардируются: последовательный прогон занимал ~7.5 минут и был
|
||||
# критическим путём всего Validate. Три шарда режут его примерно вдвое;
|
||||
@@ -857,6 +906,9 @@ jobs:
|
||||
# Бандл приходит артефактом из job `frontend` (#336): та же сборка, но
|
||||
# один раз на прогон. Свежесть проверяет сам продукт — `assertFreshDemoBundle`
|
||||
# сверяет вшитый отпечаток с исходниками этого дерева.
|
||||
- name: Очистить закоммиченный dist перед артефактом
|
||||
run: |
|
||||
node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"
|
||||
- name: Забрать собранный бандл
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
@@ -928,7 +980,8 @@ jobs:
|
||||
name: "Смоки: все шарды зелёные"
|
||||
needs: [changes, smoke, reuse]
|
||||
if: needs.changes.outputs.heavy == 'true' && needs.reuse.outputs.smoke != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Записать маркер успеха
|
||||
run: |
|
||||
@@ -952,7 +1005,8 @@ jobs:
|
||||
# inexpensive and catches a different class of regressions than timings.
|
||||
needs: [changes, frontend, reuse]
|
||||
if: needs.changes.outputs.heavy == 'true' && needs.reuse.outputs.golden != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
@@ -977,6 +1031,9 @@ jobs:
|
||||
# Бандл приходит артефактом из job `frontend` (#336): та же сборка, но
|
||||
# один раз на прогон. Свежесть проверяет сам продукт — `assertFreshDemoBundle`
|
||||
# сверяет вшитый отпечаток с исходниками этого дерева.
|
||||
- name: Очистить закоммиченный dist перед артефактом
|
||||
run: |
|
||||
node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"
|
||||
- name: Забрать собранный бандл
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
@@ -1051,7 +1108,7 @@ jobs:
|
||||
# prereleases. The expensive same-runner comparison lives in performance.yml.
|
||||
needs: [changes, frontend, reuse]
|
||||
if: needs.changes.outputs.heavy == 'true' && needs.reuse.outputs.performance_smoke != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
# 15 минут не хватало, когда установка браузера шла через apt: замер
|
||||
# начинался на исходе окна (#206). Запас на холодный кэш — при попадании
|
||||
# job укладывается в те же минуты, что и раньше. Диффозависимые профили
|
||||
@@ -1081,6 +1138,9 @@ jobs:
|
||||
# Бандл приходит артефактом из job `frontend` (#336): та же сборка, но
|
||||
# один раз на прогон. Свежесть проверяет сам продукт — `assertFreshDemoBundle`
|
||||
# сверяет вшитый отпечаток с исходниками этого дерева.
|
||||
- name: Очистить закоммиченный dist перед артефактом
|
||||
run: |
|
||||
node -e "require('node:fs').rmSync('dist', { recursive: true, force: true })"
|
||||
- name: Забрать собранный бандл
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
@@ -1144,7 +1204,8 @@ jobs:
|
||||
name: "Геометрия: TS/Python parity исполнена"
|
||||
needs: [changes, reuse]
|
||||
if: needs.changes.outputs.geometry_parity == 'true' && needs.reuse.outputs.geometry_parity != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
@@ -1178,7 +1239,8 @@ jobs:
|
||||
name: "Бэкенд: pytest в Home Assistant"
|
||||
needs: [changes, reuse]
|
||||
if: needs.changes.outputs.backend == 'true' && needs.reuse.outputs.backend != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
# Browser fixtures are generated by their real ESM factories and then
|
||||
@@ -1264,7 +1326,8 @@ jobs:
|
||||
name: "Доказательство выполненных проверок"
|
||||
if: always()
|
||||
needs: [preflight, changes, reuse, hacs, hassfest, changed_mutants, frontend, smoke, smoke_done, golden, performance_smoke, geometry_parity, backend]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
|
||||
@@ -110,7 +110,7 @@ Start with the spec?" is the correct answer, not a smaller patch.
|
||||
| **A — product** | `src/**`, `custom_components/houseplan/**/*.py`, `manifest.json`, `hacs.json`, i18n, `custom_components/**/translations/**` | yes |
|
||||
| **B — gates and tooling** | `test/**`, `tests_backend/**`, `demo/**`, `scripts/**`, `.github/workflows/**`, `rollup.config.mjs`, `tsconfig*.json` | yes; may reuse the issue it covers |
|
||||
| **C — documentation** | `docs/**`, `README*`, `CHANGELOG*`, `AGENTS.md` | not if it is part of its issue's DoD |
|
||||
| **D — generated** | `dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**` | never changes on its own. The stand copy `demo/srv/assets/**` is no longer committed (#255): build the complete tree with `npm run bundle:sync` |
|
||||
| **D — generated** | `dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**` | never changes on its own. The stand copy `demo/srv/assets/**` is no longer committed (#255): build it with `npm run bundle:sync`. The bundle (`dist/**`, `custom_components/houseplan/frontend/**`) changes only in a commit with a `Release:` trailer — the beta/release candidate, `npm run bundle:release` (#657); an ordinary task leaves it behind the sources and restores the build with `npm run bundle:clean` before committing |
|
||||
|
||||
The table above is a summary; `PROCESS.md` §1 is the authority and now covers the
|
||||
configuration files this one omits — `package.json`, `package-lock.json`,
|
||||
@@ -180,11 +180,11 @@ in between.
|
||||
|
||||
If the rebase conflicts the pipeline says so in the issue and sends the task back
|
||||
to `S6-in-progress`. The verdict still stands: nothing needs reviewing again, the
|
||||
remaining work is the rebase. When the conflict is only in the committed bundle
|
||||
(`dist/**`, `custom_components/houseplan/frontend/**` — the usual case when two
|
||||
tasks built it in parallel), run `node scripts/rebase-on-dev.mjs` (#479): it takes
|
||||
`dev`'s copy through the rebase, rebuilds with `npm run bundle:sync` and amends
|
||||
the result into your last commit; a conflict in `docs/reviews/INDEX.md` is
|
||||
remaining work is the rebase. When the conflict is only in generated files, run
|
||||
`node scripts/rebase-on-dev.mjs` (#479): a conflict in the committed bundle
|
||||
(`dist/**`, `custom_components/houseplan/frontend/**` — possible only for a branch
|
||||
started before #657, which stopped tasks from committing it) takes `dev`'s copy
|
||||
and continues, with no rebuild and no amend; a conflict in `docs/reviews/INDEX.md` is
|
||||
rebuilt from the directory (#643, the same helper the pipeline uses, so the
|
||||
pipeline no longer bounces a task on it); a conflict anywhere else aborts and
|
||||
leaves the tree as it was. Then push the branch and re-apply `S7-code-review`. When the
|
||||
@@ -399,14 +399,23 @@ npm run inventory # the only correct way to get test counts
|
||||
|
||||
Never copy test counts into documents by hand; they go stale in days.
|
||||
|
||||
After building, keep the complete manifest-driven bundle trees in sync — CI
|
||||
verifies every listed file byte-for-byte:
|
||||
After building, lay the bundle out for the stand; only a candidate updates the
|
||||
committed copy (#657):
|
||||
|
||||
```
|
||||
npm run bundle:sync # dist → custom_components + demo/srv/assets (#255)
|
||||
npm run bundle:budget # initial View graph <= 256000 B gzip (#337)
|
||||
npm run bundle:sync # build + dist → demo/srv/assets (#255)
|
||||
npm run bundle:clean # before an ordinary commit: dist back to the committed copy (#657)
|
||||
npm run bundle:release # candidate only: build + dist → custom_components + demo/srv/assets
|
||||
npm run bundle:budget # initial View graph within INITIAL_VIEW_GZIP_BUDGET (scripts/bundle-budget.mjs, #337/#367)
|
||||
```
|
||||
|
||||
CI (`bundle-policy --verify`) checks the fresh build's integrity on every push
|
||||
and compares it byte-for-byte with the committed copy only where the commit
|
||||
changes the bundle or is a candidate. Publishing a beta additionally refuses a
|
||||
committed bundle whose embedded source fingerprint differs from the tree. The
|
||||
dev stand gets the head of `dev` from the Validate artifact via the `dev-build`
|
||||
branch, not from the tree (`demo/stand/README.md`).
|
||||
|
||||
`npm run gate:small` runs the mandatory part of PROCESS §8 in one go (#479,
|
||||
#576): build with typecheck, `no-new-any`, `no-new-private-writes` and `smoke-select` start in parallel;
|
||||
unit tests follow the completed build because their bundle-contract witnesses
|
||||
|
||||
@@ -104,9 +104,12 @@ tree repeatedly. Do **not** replace it with `--depth=1`: a shallow clone is abou
|
||||
the same size but has no `merge-base`, so the process gate, `smoke-select` and
|
||||
every `origin/dev..HEAD` range stop working.
|
||||
|
||||
The HA-harness backend tests (`tests_backend/test_ha_*.py`) need Python ≥3.13 and
|
||||
`pytest-homeassistant-custom-component home-assistant-frontend`; CI runs them on
|
||||
every push — locally they are skipped when `homeassistant` is not importable.
|
||||
The HA-harness backend tests (`tests_backend/test_ha_*.py`) need the
|
||||
repository-pinned Python (`.python-version`) and
|
||||
`pytest-homeassistant-custom-component home-assistant-frontend`; their canon is
|
||||
Linux CI or WSL (`bash scripts/wsl-setup.sh --verify`). Without an importable
|
||||
`homeassistant` they are not collected at all — not skipped — and pytest prints
|
||||
`HA harness NOT collected: …` (#630).
|
||||
|
||||
## Ground rules
|
||||
|
||||
@@ -114,8 +117,10 @@ every push — locally they are skipped when `homeassistant` is not importable.
|
||||
`docs/STATUS.md` for state changes; `docs/DEVELOPMENT.md` for new gotchas.
|
||||
- Every UI string goes through `src/i18n/<lang>.json`; follow the
|
||||
[Translations](#translations) flow for registry and backend parity.
|
||||
- The built card must be committed in sync: `cp dist/houseplan-card.js
|
||||
custom_components/houseplan/frontend/` (CI compares them byte-for-byte).
|
||||
- The committed bundle changes only in a release candidate: `npm run
|
||||
bundle:release` in a commit with a `Release:` trailer (#657). An ordinary task
|
||||
restores it with `npm run bundle:clean` before committing — the `commit-msg`
|
||||
hook refuses a bundle change otherwise.
|
||||
- Tap actions have a security model (locks/alarms never toggle from the plan) —
|
||||
see `resolveToggleIntent` in `src/device-toggle.ts`; don't weaken it.
|
||||
- Every commit follows the issue and trailer contract in `PROCESS.md`.
|
||||
@@ -125,6 +130,8 @@ every push — locally they are skipped when `homeassistant` is not importable.
|
||||
## Architecture
|
||||
|
||||
Start with `docs/ARCHITECTURE.md` (data model, WS API, coordinate system) and
|
||||
`docs/STATUS.md` (current state). Release: bump the version in `package.json`,
|
||||
`manifest.json`, `const.py`, `CARD_VERSION`, tag `vX.Y.Z`, publish a GitHub release —
|
||||
the workflow attaches the card bundle.
|
||||
`docs/STATUS.md` (current state). Release mechanics live in `docs/STATUS.md` › Workflow: the version sources are
|
||||
the ones checked by `scripts/release-contract.mjs`, prereleases go through
|
||||
`npm run release:prerelease -- <tag> --issues=… --yes` (or the manual
|
||||
`Publish prerelease` workflow), and stable installable assets are published only
|
||||
by `release.yml` (#540).
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
| **A. Продукт** | `src/**`, `custom_components/houseplan/**/*.py`, `manifest.json`, `hacs.json`, `src/i18n/*.json`, `custom_components/**/translations/*` | **Да, обязательно.** Только из «Готово к разработке» или дальше |
|
||||
| **B. Гейты и инструменты** | `test/**`, `tests_backend/**`, `demo/**`, `scripts/**`, весь `.github/**`, `.githooks/**`, `rollup.config.mjs`, `tsconfig*.json`, `package.json`, `package-lock.json`, `pytest.ini`, `.gitignore`, `.gitattributes` | **Да.** Может использовать issue того изменения, которое покрывает; самостоятельная работа над гейтом получает свой issue (тип `tech-debt`) |
|
||||
| **C. Документация** | `docs/**`, `README*`, `CHANGELOG*`, `AGENTS.md`, `CONTRIBUTING.md`, `PROCESS*.md`, `LICENSE`, `(CODE\|SPEC)-REVIEW-*.md` | Документирование A/B в том же коммите — часть DoD своего issue. Самостоятельная работа над документацией — свой issue |
|
||||
| **D. Сгенерированное** | `dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**` (копия стенда `demo/srv/assets/houseplan-card.js` с #255 не коммитится вовсе) | Никогда не меняется само по себе. Коммит **только** класса D допустим лишь как релизный промоушен или как принятие эталонов с доказательством ревью |
|
||||
| **D. Сгенерированное** | `dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**` (копия стенда `demo/srv/assets/houseplan-card.js` с #255 не коммитится вовсе) | Никогда не меняется само по себе. Коммит **только** класса D допустим лишь как релизный промоушен или как принятие эталонов с доказательством ревью. **Бандл** (`dist/**`, `custom_components/houseplan/frontend/**`) с #657 меняет только коммит с трейлером `Release:` — кандидат беты или релиза (`npm run bundle:release`); в обычной задаче закоммиченный бандл законно отстаёт от исходников, сборка в коммит не идёт (`npm run bundle:clean`). Судит `validate-commit-provenance.mjs` — хук `commit-msg` и история в CI |
|
||||
|
||||
Практический смысл таблицы: «я только поправил тест» и «я только пересобрал
|
||||
бандл» перестают быть лазейками.
|
||||
@@ -363,10 +363,13 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
||||
**Индекс документов ревью** — `docs/reviews/INDEX.md` (#635): одна строка на
|
||||
документ — issue, этап, раунд, вердикт, число High/Medium, заголовки находок,
|
||||
файлы из находок (искать по имени файла: `grep form-kit docs/reviews/INDEX.md`).
|
||||
Файл генерируется `node scripts/reviews-index.mjs` и пересобирается конвейером
|
||||
тем же коммитом, что публикует документ, а также после каждого его ребейза
|
||||
(приведение к dev перед ревью, слияние кандидата — `--commit-if-stale`, коммит
|
||||
класса C); руками не правится. Конфликт ребейза, в котором **все** пути —
|
||||
Файл генерируется `node scripts/reviews-index.mjs` и пересобирается **только
|
||||
коммитами, идущими в `dev`** (#657, решение 1б): слиянием кандидата —
|
||||
после ребейза, а если `dev` не двигался, то поверх материала перед
|
||||
fast-forward (`--commit-if-stale`, коммит класса C) — и публикацией документа ревью ТЗ
|
||||
прямо в `dev`. В ветке задачи индекс не пересобирается — ни при приведении к
|
||||
dev, ни при публикации документа код-ревью: иначе две параллельные задачи
|
||||
конфликтуют на нём по построению. Руками не правится. Конфликт ребейза, в котором **все** пути —
|
||||
`INDEX.md`, отказом не считается (#643): `scripts/rebase-generated.mjs`
|
||||
пересобирает индекс по каталогу на остановке и продолжает ребейз — так делают
|
||||
приведение к dev, слияние кандидата и авторский `rebase-on-dev.mjs`; индекс
|
||||
@@ -388,7 +391,7 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
||||
перечисляет только живые. Перенос — часть чеклиста стабильного релиза, не
|
||||
отдельная задача.
|
||||
|
||||
Дешёвые гейты (`typecheck`, `test`, `build` со сверкой копий бандла) гоняются в
|
||||
Дешёвые гейты (`typecheck`, `test`, `build` с проверкой целостности сборки, `bundle-policy --verify`) гоняются в
|
||||
каждом раунде: код изменился, а стоят они минуты. Тяжёлые — по дельте (§10.2).
|
||||
|
||||
**Разбор остаётся полным**, если дельта не локальна: ребейз на ушедший вперёд
|
||||
@@ -707,8 +710,9 @@ issue #NN
|
||||
```
|
||||
npx tsc --noEmit
|
||||
npm test
|
||||
npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js \
|
||||
# копия стенда собирается `npm run bundle:sync`, в репозитории её нет (#255)
|
||||
npm run build && node scripts/bundle-policy.mjs --verify HEAD
|
||||
# сборка цела; копии сверяются только на кандидате (#657).
|
||||
# Копия стенда — `npm run bundle:sync` (#255); перед коммитом `npm run bundle:clean`
|
||||
node scripts/smoke-select.mjs --base origin/dev --head HEAD # какие смоки относятся к диффу
|
||||
node demo/smoke_<целевые>.mjs
|
||||
node scripts/no-new-any.mjs --base origin/dev --head HEAD # новый код не добавляет any
|
||||
@@ -732,7 +736,7 @@ npx tsc -p tsconfig.junction-parity.json && node scripts/fix-test-build.mjs \
|
||||
комментарии, строке или идентификаторе ложных срабатываний не даёт.
|
||||
|
||||
**Объём гейтов на код-ревью соразмерен задаче** (issue #127). Всегда:
|
||||
`typecheck`, `npm test`, `npm run build` со сверкой трёх копий бандла, а при
|
||||
`typecheck`, `npm test`, `npm run build` с `bundle-policy --verify` (копии сверяются на кандидате, #657), а при
|
||||
любом diff'е по `src/**` — ещё и `node scripts/check-docs.mjs`. По
|
||||
необходимости, определяемой diff'ом и AC: браузерные смоки (сколько их —
|
||||
считает `ls demo/smoke_*.mjs | wc -l`, вшитое число здесь трижды отставало от
|
||||
@@ -1067,7 +1071,7 @@ Matysh/houseplan-card/.github/workflows/_<имя>.yml@dev` с `secrets: inherit`
|
||||
мутантов не запрашивают** (#601, решение владельца 20.09): мутационный гейт
|
||||
проверяет тесты, а не продукт (#513), к бете каждая задача прогнана им дважды —
|
||||
на ревью и на слитом после ребейза кандидате, — а ночью идёт полный реестр
|
||||
(`mutation-gate.yml`, 01:00 UTC). Релизный гейт (#541) требует полного Validate,
|
||||
(`mutation-gate.yml`, 00:43 UTC). Релизный гейт (#541) требует полного Validate,
|
||||
но не mutant-jobs; для ревью и слияния шесть исполненных mutant-jobs остаются
|
||||
обязательными.
|
||||
|
||||
@@ -1324,6 +1328,24 @@ Golden, браузерные смоки, performance и полный HA-харн
|
||||
`gh workflow run release-review.yml --ref dev -f tag=vX.Y.Z [-f candidate=<sha>]`.
|
||||
Беты шаг пропускают. Первый прогон — линия v1.78.0.
|
||||
|
||||
### 11.6 Повторные Validate на одном SHA перед релизом
|
||||
|
||||
Решение владельца 2026-09-26, issue #656.
|
||||
|
||||
Релизный гейт рассматривает прогоны одного SHA от нового к старому. Отменённый
|
||||
прогон и proof, который не соответствует запрошенной политике (например,
|
||||
лёгкий вместо полного), вердиктом не являются: гейт проходит мимо них к
|
||||
следующему совместимому proof. **Среди совместимых полных прогонов решает
|
||||
новейший.** Поэтому поздний полный `failed`, `missing` или `pending` блокирует
|
||||
более ранний зелёный proof; новый полный зелёный прогон может обновить старый
|
||||
красный.
|
||||
|
||||
Это fail-closed правило. Content-addressed proof доказывает, что конкретный
|
||||
прогон относится к кандидату, но не даёт старому зелёному прогону права скрыть
|
||||
более позднюю проверку той же политики, которая нашла отказ. Чтобы продолжить
|
||||
выпуск после такого отказа, исправляют причину и получают новый совместимый
|
||||
полный зелёный прогон на том же финальном SHA либо на новом SHA кандидата.
|
||||
|
||||
---
|
||||
|
||||
## 12. Запрещено
|
||||
|
||||
@@ -42,6 +42,9 @@ sync across screens.
|
||||
show temperature, humidity, light state and average LQI.
|
||||
- **Light and environment.** Room fills, lamp Glow, wall shadows, a day-cycle
|
||||
backdrop and sunlight through windows.
|
||||
- **Flat or 2.5D.** One switch in General settings › Display shows the plan
|
||||
with depth everywhere: raised device tiles with soft floor shadows and a soft
|
||||
wash of sunlight; decor and wall colours stay as you set them.
|
||||
- **Doors, windows, gates and vacuums.** Openings follow real contacts and locks;
|
||||
a robot can show its position, dock and travelled path.
|
||||
- **Several floors and screens.** Space tabs, swipe navigation, local viewport,
|
||||
@@ -58,8 +61,8 @@ sync across screens.
|
||||
1. Install the integration and open **House Plan** in the Home Assistant sidebar.
|
||||
2. Create the first **space**: upload SVG/PNG/JPG/WebP, reuse an uploaded image,
|
||||
or choose no image and draw the plan by hand.
|
||||
3. In Plan, select **Room outline**, place vertices, and click the first point to
|
||||
close the outline.
|
||||
3. In Plan, select **Walls** and draw one continuous chain around the room: when
|
||||
it closes an area, the room dialog opens.
|
||||
4. Name the room and bind it to a Home Assistant area. Use “No area” for a room
|
||||
that has no devices.
|
||||
5. Open Device: devices from the bound area are already placed; drag their
|
||||
@@ -183,6 +186,7 @@ concurrent clients, but avoid editing the same object in two browsers at once.
|
||||
- [Full user guide](docs/USER-GUIDE.md)
|
||||
- [Mouse/touch/keyboard matrix](docs/USER-GUIDE.md#6-navigation-zoom-and-input)
|
||||
- [Plan tools](docs/USER-GUIDE.md#plan-tools-at-a-glance)
|
||||
- [Stairs and floor links](docs/STAIRS.md)
|
||||
- [Background editor](docs/DECOR-EDITOR.md)
|
||||
- [Robot vacuums](docs/VACUUM.md)
|
||||
- [Touch support](docs/TOUCH-SUPPORT.md)
|
||||
|
||||
@@ -59,8 +59,8 @@ Assistant — и устройства появятся на плане авто
|
||||
1. Установите интеграцию и откройте **House Plan** в боковом меню Home Assistant.
|
||||
2. Создайте первое **пространство**: загрузите SVG/PNG/JPG/WebP либо выберите
|
||||
вариант без изображения, чтобы нарисовать план вручную.
|
||||
3. В редакторе «План» выберите **Контур комнаты**, поставьте вершины и замкните
|
||||
контур нажатием на первую точку.
|
||||
3. В редакторе «План» выберите **Стены** и нарисуйте непрерывную цепочку по
|
||||
периметру комнаты: когда она замкнёт область, откроется диалог комнаты.
|
||||
4. Назовите комнату и свяжите её с зоной Home Assistant. Для помещения без
|
||||
устройств выберите «Без зоны».
|
||||
5. Откройте «Устройства»: устройства связанной зоны уже размещены автоматически;
|
||||
|
||||
@@ -6,6 +6,7 @@ import logging
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
@@ -39,6 +40,30 @@ from .store import (
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def _async_flush_runtime(
|
||||
hass: HomeAssistant, entry: HouseplanConfigEntry,
|
||||
) -> None:
|
||||
"""Stop deferred writers and persist their latest state once.
|
||||
|
||||
Home Assistant stop and config-entry unload share this exact path. Both
|
||||
writers are independently guarded so one failed store cannot prevent the
|
||||
other from reaching disk during shutdown.
|
||||
"""
|
||||
recorder = hass.data.get(DOMAIN, {}).get("trail_recorder")
|
||||
if recorder is not None:
|
||||
try:
|
||||
await recorder.async_teardown()
|
||||
except Exception: # noqa: BLE001 - shutdown must continue with other stores
|
||||
_LOGGER.exception("House Plan: flushing vacuum trails failed")
|
||||
|
||||
virtual_lights = getattr(entry.runtime_data, "virtual_lights", None)
|
||||
if virtual_lights is not None:
|
||||
try:
|
||||
await virtual_lights.async_flush()
|
||||
except Exception: # noqa: BLE001 - shutdown must continue with other stores
|
||||
_LOGGER.exception("House Plan: flushing virtual-light state failed")
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config) -> bool:
|
||||
"""Register global handlers (survive config-entry reloads): WS commands, HTTP view."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
@@ -252,6 +277,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: HouseplanConfigEntry) ->
|
||||
# sidebar entry after a migration, repair or initial housekeeping failure.
|
||||
panel_path = Path(__file__).parent / "frontend" / "houseplan-panel.js"
|
||||
await async_setup_panel_registration(hass, entry, panel_path)
|
||||
|
||||
async def _flush_on_stop(_event) -> None:
|
||||
stop_listener[0] = None
|
||||
await _async_flush_runtime(hass, entry)
|
||||
|
||||
stop_listener = [hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _flush_on_stop)]
|
||||
|
||||
def _remove_stop_listener() -> None:
|
||||
if stop_listener[0] is not None:
|
||||
stop_listener[0]()
|
||||
stop_listener[0] = None
|
||||
|
||||
entry.async_on_unload(_remove_stop_listener)
|
||||
return True
|
||||
|
||||
|
||||
@@ -263,12 +301,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: HouseplanConfigEntry) -
|
||||
entry is loaded. Static paths cannot be unregistered by design.
|
||||
"""
|
||||
remove_panel_registration(hass)
|
||||
rec = hass.data.get(DOMAIN, {}).pop("trail_recorder", None)
|
||||
if rec:
|
||||
await rec.async_teardown()
|
||||
virtual_lights = getattr(entry.runtime_data, "virtual_lights", None)
|
||||
if virtual_lights is not None:
|
||||
await virtual_lights.async_flush()
|
||||
await _async_flush_runtime(hass, entry)
|
||||
hass.data.get(DOMAIN, {}).pop("trail_recorder", None)
|
||||
if entry.runtime_data.radar_coordinator:
|
||||
entry.runtime_data.radar_coordinator.teardown()
|
||||
entry.runtime_data.radar_coordinator = None
|
||||
|
||||
@@ -57,7 +57,7 @@ SCHEDULED_GRACE_S = 30 * 24 * 3600
|
||||
FILES_DIR = "houseplan/files"
|
||||
ASSETS_DIR = "houseplan/assets"
|
||||
CONF_ADMIN_ONLY = "admin_only"
|
||||
VERSION = "1.78.0-beta.2"
|
||||
VERSION = "1.78.0-beta.5"
|
||||
DECOR_ASSETS_API_VERSION = 1
|
||||
SUMMARY_PANEL_API_VERSION = 1
|
||||
|
||||
|
||||
@@ -158,6 +158,12 @@ def canonicalize_config_geometry(config: Any) -> Any:
|
||||
_lattice_fields(decor, ("x", "y"))
|
||||
_scalar_fields(decor, ("scale", "angle"))
|
||||
|
||||
for stair in _records(space.get("stairs")):
|
||||
# #663: wall/stair magnet placement is continuous like furniture.
|
||||
# Scalar cleanup removes JSON tails without moving an authored
|
||||
# physical-face contact onto the plan grid.
|
||||
_scalar_fields(stair, ("x", "y", "length", "width", "radius", "angle"))
|
||||
|
||||
# Legacy-only read boundary: v9 drafts are canonicalized before the
|
||||
# wall-model migration turns their edges into current partitions.
|
||||
for draft in _records(space.get("room_drafts")):
|
||||
|
||||
@@ -33,6 +33,7 @@ async def async_get_config_entry_diagnostics(
|
||||
"rooms_with_area": sum(1 for r in s.get("rooms", []) if r.get("area")),
|
||||
"partitions": len(s.get("partitions", [])),
|
||||
"wall_columns": len(s.get("wall_columns", [])),
|
||||
"stairs": len(s.get("stairs", [])),
|
||||
}
|
||||
for s in config.get("spaces", [])
|
||||
],
|
||||
|
||||
@@ -1,129 +1,129 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"fingerprint": "5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051",
|
||||
"fingerprint": "d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96",
|
||||
"entry": "houseplan-card.js",
|
||||
"panelEntry": "houseplan-panel.js",
|
||||
"initialViewFiles": [
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js",
|
||||
"houseplan-card.js"
|
||||
],
|
||||
"initialViewGzipBytes": 291761,
|
||||
"initialViewGzipBytes": 299093,
|
||||
"initialPanelFiles": [
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js",
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelGzipBytes": 293284,
|
||||
"initialPanelGzipBytes": 300614,
|
||||
"initialPanelOnlyFiles": [
|
||||
"houseplan-panel.js"
|
||||
],
|
||||
"initialPanelOnlyGzipBytes": 2326,
|
||||
"initialPanelOnlyGzipBytes": 2324,
|
||||
"lazyFiles": [
|
||||
"houseplan-assets/backdrop-pick-D3-yn5HD.js",
|
||||
"houseplan-assets/de-C7gyFGpd.js",
|
||||
"houseplan-assets/editor-9cyNd7R7.js",
|
||||
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
|
||||
"houseplan-assets/fr-BSn1zqYb.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-DFhn-zc9.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BiiaK_3r.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-D7siZQoq.js",
|
||||
"houseplan-assets/iso-scene-render-xsesUj3A.js",
|
||||
"houseplan-assets/live-interaction-runtime-DZWBYZP0.js",
|
||||
"houseplan-assets/namespace-language-B2Cyshho.js",
|
||||
"houseplan-assets/pdf-export-BYsRpVk_.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-gtNYqb9U.js",
|
||||
"houseplan-assets/zigbee-topology-CvQsfSze.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-A7rG7kis.js"
|
||||
"houseplan-assets/backdrop-pick-CXaZKwRy.js",
|
||||
"houseplan-assets/de-B2abA_bU.js",
|
||||
"houseplan-assets/editor-BLL8Ue28.js",
|
||||
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
|
||||
"houseplan-assets/fr-Bho0FfMD.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-FEM5fqpW.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CiQlpis3.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-BeNZqp-_.js",
|
||||
"houseplan-assets/iso-scene-render-Soh6e8O4.js",
|
||||
"houseplan-assets/live-interaction-runtime-D6A_AoBE.js",
|
||||
"houseplan-assets/namespace-language-CwX4Ljv1.js",
|
||||
"houseplan-assets/pdf-export-De59asII.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-jjP-5y3_.js",
|
||||
"houseplan-assets/zigbee-topology-BbetEPvj.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-sjaKOGGU.js"
|
||||
],
|
||||
"lazyGzipBytes": 449074,
|
||||
"lazyGzipBytes": 453435,
|
||||
"lazyEditorFiles": [
|
||||
"houseplan-assets/backdrop-pick-D3-yn5HD.js",
|
||||
"houseplan-assets/editor-9cyNd7R7.js",
|
||||
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-DFhn-zc9.js",
|
||||
"houseplan-assets/namespace-language-B2Cyshho.js",
|
||||
"houseplan-assets/zigbee-topology-CvQsfSze.js"
|
||||
"houseplan-assets/backdrop-pick-CXaZKwRy.js",
|
||||
"houseplan-assets/editor-BLL8Ue28.js",
|
||||
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-FEM5fqpW.js",
|
||||
"houseplan-assets/namespace-language-CwX4Ljv1.js",
|
||||
"houseplan-assets/zigbee-topology-BbetEPvj.js"
|
||||
],
|
||||
"lazyEditorGzipBytes": 231934,
|
||||
"lazyEditorGzipBytes": 235435,
|
||||
"lazyOnboardingFiles": [
|
||||
"houseplan-assets/backdrop-pick-D3-yn5HD.js",
|
||||
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BiiaK_3r.js",
|
||||
"houseplan-assets/namespace-language-B2Cyshho.js"
|
||||
"houseplan-assets/backdrop-pick-CXaZKwRy.js",
|
||||
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CiQlpis3.js",
|
||||
"houseplan-assets/namespace-language-CwX4Ljv1.js"
|
||||
],
|
||||
"lazyOnboardingGzipBytes": 27899,
|
||||
"lazyOnboardingGzipBytes": 27952,
|
||||
"lazyLocaleFiles": [
|
||||
"houseplan-assets/de-C7gyFGpd.js",
|
||||
"houseplan-assets/fr-BSn1zqYb.js"
|
||||
"houseplan-assets/de-B2abA_bU.js",
|
||||
"houseplan-assets/fr-Bho0FfMD.js"
|
||||
],
|
||||
"lazyLocaleGzipBytes": 55699,
|
||||
"lazyLocaleGzipBytes": 56230,
|
||||
"lazyIsometricFiles": [
|
||||
"houseplan-assets/iso-scene-render-xsesUj3A.js"
|
||||
"houseplan-assets/iso-scene-render-Soh6e8O4.js"
|
||||
],
|
||||
"lazyIsometricGzipBytes": 17643,
|
||||
"lazyIsometricGzipBytes": 17885,
|
||||
"lazyFurnitureArtFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js"
|
||||
],
|
||||
"lazyFurnitureArtGzipBytes": 16940,
|
||||
"lazyFurnitureArtGzipBytes": 16941,
|
||||
"lazyPdfFiles": [
|
||||
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
|
||||
"houseplan-assets/pdf-export-BYsRpVk_.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
|
||||
"houseplan-assets/pdf-export-De59asII.js"
|
||||
],
|
||||
"lazyPdfGzipBytes": 126827,
|
||||
"lazyPdfGzipBytes": 126914,
|
||||
"lazyNamespaceLocaleFiles": [
|
||||
"houseplan-assets/settings-de-BSeiuZ78.js",
|
||||
"houseplan-assets/settings-fr-Dqa7IIGC.js",
|
||||
"houseplan-assets/settings-ru-DTjpLI7z.js",
|
||||
"houseplan-assets/support-de-DkCHsIJX.js",
|
||||
"houseplan-assets/support-fr-CF5yiuHl.js",
|
||||
"houseplan-assets/support-ru-Bs7OI6GX.js",
|
||||
"houseplan-assets/topology-de-BelPUGaW.js",
|
||||
"houseplan-assets/topology-fr-FvLCzTYk.js",
|
||||
"houseplan-assets/topology-ru-DG2M1Xdq.js"
|
||||
"houseplan-assets/settings-de-BjfMuSw8.js",
|
||||
"houseplan-assets/settings-fr-BKCX3R6z.js",
|
||||
"houseplan-assets/settings-ru-D8xvcCw6.js",
|
||||
"houseplan-assets/support-de-C9Oo6umO.js",
|
||||
"houseplan-assets/support-fr-CRCSYq8q.js",
|
||||
"houseplan-assets/support-ru-BcRlKfAd.js",
|
||||
"houseplan-assets/topology-de-BjBB8qiK.js",
|
||||
"houseplan-assets/topology-fr-DXFxoYd-.js",
|
||||
"houseplan-assets/topology-ru-Di7X71yN.js"
|
||||
],
|
||||
"lazyNamespaceLocaleGzipBytes": 20887,
|
||||
"lazyNamespaceLocaleGzipBytes": 21047,
|
||||
"files": [
|
||||
{
|
||||
"path": "houseplan-assets/backdrop-pick-D3-yn5HD.js",
|
||||
"sha256": "9979ee2d9318b8f709993cc91c77968c12ecfb3c3692a16d2ac3a268d5a752c2",
|
||||
"rawBytes": 53888,
|
||||
"gzipBytes": 17524,
|
||||
"path": "houseplan-assets/backdrop-pick-CXaZKwRy.js",
|
||||
"sha256": "001e8011f17ee8c1d3804304eb6734065b645a28b19beb8c18fa699f309de66c",
|
||||
"rawBytes": 54017,
|
||||
"gzipBytes": 17577,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
|
||||
"houseplan-assets/namespace-language-B2Cyshho.js"
|
||||
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js",
|
||||
"houseplan-assets/namespace-language-CwX4Ljv1.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/settings-de-BSeiuZ78.js",
|
||||
"houseplan-assets/settings-fr-Dqa7IIGC.js",
|
||||
"houseplan-assets/settings-ru-DTjpLI7z.js"
|
||||
"houseplan-assets/settings-de-BjfMuSw8.js",
|
||||
"houseplan-assets/settings-fr-BKCX3R6z.js",
|
||||
"houseplan-assets/settings-ru-D8xvcCw6.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/de-C7gyFGpd.js",
|
||||
"sha256": "eed3905d3c370c5071e7074d4753a769efd3d37d263f21cee2a135ef0dbd6a2c",
|
||||
"rawBytes": 93083,
|
||||
"gzipBytes": 28075,
|
||||
"path": "houseplan-assets/de-B2abA_bU.js",
|
||||
"sha256": "63b70f53f23e194e16172c10b11dda1d19db0bebc23a5213444cab07da1e72be",
|
||||
"rawBytes": 93993,
|
||||
"gzipBytes": 28342,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/editor-9cyNd7R7.js",
|
||||
"sha256": "e49468af916efc53baab7dd87825f20b2b2b957dbcae134cfa493e0c9b08ae3a",
|
||||
"path": "houseplan-assets/editor-BLL8Ue28.js",
|
||||
"sha256": "6176410d822670169447e2324d7af4ffc7f500e914fd597b0318c41785adb31c",
|
||||
"rawBytes": 3833,
|
||||
"gzipBytes": 1583,
|
||||
"gzipBytes": 1581,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/form-kit.styles-BJpA5NGj.js",
|
||||
"sha256": "e07f6da05bf8bff00363bc559c8f9ac2c7acc93d0141d37a7570bd02ec1192ca",
|
||||
"path": "houseplan-assets/form-kit.styles-CCrtJjwP.js",
|
||||
"sha256": "e45b8023f44f1048304d5d43616f51ea797f9a3475a801f776f9c488b93a1435",
|
||||
"rawBytes": 25600,
|
||||
"gzipBytes": 5056,
|
||||
"isEntry": false,
|
||||
@@ -131,207 +131,207 @@
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/fr-BSn1zqYb.js",
|
||||
"sha256": "f0b4b416865b978983c49e1b3321ca3d6f3956a43f7b1576d23e2d81b1a80148",
|
||||
"rawBytes": 95424,
|
||||
"gzipBytes": 27624,
|
||||
"path": "houseplan-assets/fr-Bho0FfMD.js",
|
||||
"sha256": "ae1939c095197487f36b8d34e06f976ac8e665f3542d9dc9cf06190963310ac3",
|
||||
"rawBytes": 96369,
|
||||
"gzipBytes": 27888,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
|
||||
"sha256": "b5e9e8743651feb5c515d986581bd860a2184d7d64a7744b53acd0c6d7ed0327",
|
||||
"path": "houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
|
||||
"sha256": "ae4b691e03b0869b927ab76dfce816e0e50662bb9c78de68d2a66cdd2ead163e",
|
||||
"rawBytes": 52035,
|
||||
"gzipBytes": 16940,
|
||||
"gzipBytes": 16941,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-card-BCYl6SkQ.js",
|
||||
"sha256": "6df593e8f6479b0bedf505c29a41a20b4bf53a5977b45e0bf3165400fd3769e0",
|
||||
"rawBytes": 1030968,
|
||||
"gzipBytes": 290958,
|
||||
"path": "houseplan-assets/houseplan-card-Ddo22-SB.js",
|
||||
"sha256": "8746be63df29372816cf31f039e60165f1c7906565ab25cef8de52937cd88e89",
|
||||
"rawBytes": 1055297,
|
||||
"gzipBytes": 298290,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/de-C7gyFGpd.js",
|
||||
"houseplan-assets/editor-9cyNd7R7.js",
|
||||
"houseplan-assets/fr-BSn1zqYb.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-DFhn-zc9.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-BiiaK_3r.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-D7siZQoq.js",
|
||||
"houseplan-assets/iso-scene-render-xsesUj3A.js",
|
||||
"houseplan-assets/live-interaction-runtime-DZWBYZP0.js",
|
||||
"houseplan-assets/pdf-export-BYsRpVk_.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-gtNYqb9U.js"
|
||||
"houseplan-assets/de-B2abA_bU.js",
|
||||
"houseplan-assets/editor-BLL8Ue28.js",
|
||||
"houseplan-assets/fr-Bho0FfMD.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
|
||||
"houseplan-assets/houseplan-editor-runtime-FEM5fqpW.js",
|
||||
"houseplan-assets/houseplan-onboarding-runtime-CiQlpis3.js",
|
||||
"houseplan-assets/hp-zigbee-topology-overlay-BeNZqp-_.js",
|
||||
"houseplan-assets/iso-scene-render-Soh6e8O4.js",
|
||||
"houseplan-assets/live-interaction-runtime-D6A_AoBE.js",
|
||||
"houseplan-assets/pdf-export-De59asII.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-jjP-5y3_.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-DFhn-zc9.js",
|
||||
"sha256": "c0a4a8e337a3e13b0df19eb57938c185d504e6ed89854d514585cd00b4f40197",
|
||||
"rawBytes": 667315,
|
||||
"gzipBytes": 186120,
|
||||
"path": "houseplan-assets/houseplan-editor-runtime-FEM5fqpW.js",
|
||||
"sha256": "1a2c3ca19fd2ecdfe8e4fbbe2b0d187d44dc986cf97a7d5d13a47d0edf2baad0",
|
||||
"rawBytes": 679701,
|
||||
"gzipBytes": 189570,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-D3-yn5HD.js",
|
||||
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
|
||||
"houseplan-assets/namespace-language-B2Cyshho.js",
|
||||
"houseplan-assets/zigbee-topology-CvQsfSze.js"
|
||||
"houseplan-assets/backdrop-pick-CXaZKwRy.js",
|
||||
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
|
||||
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js",
|
||||
"houseplan-assets/namespace-language-CwX4Ljv1.js",
|
||||
"houseplan-assets/zigbee-topology-BbetEPvj.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/support-de-DkCHsIJX.js",
|
||||
"houseplan-assets/support-fr-CF5yiuHl.js",
|
||||
"houseplan-assets/support-ru-Bs7OI6GX.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-A7rG7kis.js"
|
||||
"houseplan-assets/support-de-C9Oo6umO.js",
|
||||
"houseplan-assets/support-fr-CRCSYq8q.js",
|
||||
"houseplan-assets/support-ru-BcRlKfAd.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-sjaKOGGU.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-BiiaK_3r.js",
|
||||
"sha256": "0d13b9bc17fc152738912ddfb30d0fd8247984c80286cbd6b82d6609c99bff48",
|
||||
"path": "houseplan-assets/houseplan-onboarding-runtime-CiQlpis3.js",
|
||||
"sha256": "08a26f899d0a5f8f71a2ed6f71d095388aa9eead14ce4a4593ce21ee2e54ea14",
|
||||
"rawBytes": 15886,
|
||||
"gzipBytes": 4909,
|
||||
"gzipBytes": 4910,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/backdrop-pick-D3-yn5HD.js",
|
||||
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
|
||||
"houseplan-assets/namespace-language-B2Cyshho.js"
|
||||
"houseplan-assets/backdrop-pick-CXaZKwRy.js",
|
||||
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js",
|
||||
"houseplan-assets/namespace-language-CwX4Ljv1.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-D7siZQoq.js",
|
||||
"sha256": "0254f89b26d8b337c1c4939931cf72938089a0ea326199235320348ff6e085f9",
|
||||
"path": "houseplan-assets/hp-zigbee-topology-overlay-BeNZqp-_.js",
|
||||
"sha256": "5360f9165e07d274bc2e71634792f9d8868b20750f4565feeee98d48e9ac08fa",
|
||||
"rawBytes": 10958,
|
||||
"gzipBytes": 3765,
|
||||
"gzipBytes": 3764,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
|
||||
"houseplan-assets/namespace-language-B2Cyshho.js",
|
||||
"houseplan-assets/zigbee-topology-CvQsfSze.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-A7rG7kis.js"
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js",
|
||||
"houseplan-assets/namespace-language-CwX4Ljv1.js",
|
||||
"houseplan-assets/zigbee-topology-BbetEPvj.js",
|
||||
"houseplan-assets/zigbee-topology-runtime-sjaKOGGU.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/iso-scene-render-xsesUj3A.js",
|
||||
"sha256": "4f41caad95b99d01013048852c1aa4c147959a8ef10a906dde51f45808e87905",
|
||||
"rawBytes": 51983,
|
||||
"gzipBytes": 17643,
|
||||
"path": "houseplan-assets/iso-scene-render-Soh6e8O4.js",
|
||||
"sha256": "192b514d86778182905c9ade6b2f2f28e8428b281b0190ebe9b159d3170eaf98",
|
||||
"rawBytes": 52970,
|
||||
"gzipBytes": 17885,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/live-interaction-runtime-DZWBYZP0.js",
|
||||
"sha256": "aac5a7823e6fd097be076d62534e9b9f1c4290f36b92033d59bf1cac891da300",
|
||||
"path": "houseplan-assets/live-interaction-runtime-D6A_AoBE.js",
|
||||
"sha256": "8a3cd740cd67f85041975cf18d9819b6d44be34601e56d639664dce2b79b9873",
|
||||
"rawBytes": 8216,
|
||||
"gzipBytes": 3207,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/namespace-language-B2Cyshho.js",
|
||||
"sha256": "5934f29831a1aecc9503f9afdc8559844dc39e09474b51d26cc917965f27faac",
|
||||
"path": "houseplan-assets/namespace-language-CwX4Ljv1.js",
|
||||
"sha256": "46a700cfd83daaaa2ddebe209e8395e08ae6c1e84f286e1f5807c31604e90130",
|
||||
"rawBytes": 675,
|
||||
"gzipBytes": 410,
|
||||
"gzipBytes": 409,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/pdf-export-BYsRpVk_.js",
|
||||
"sha256": "7a4a4d2130fb379cd99681cb6a843e10f81d954bf3c6146d1843bdc9b29027b9",
|
||||
"rawBytes": 236264,
|
||||
"gzipBytes": 109887,
|
||||
"path": "houseplan-assets/pdf-export-De59asII.js",
|
||||
"sha256": "4f1c72412bade0a94e89cc49fb44cce1b90c5304186506d0254fd0de25b26c31",
|
||||
"rawBytes": 236608,
|
||||
"gzipBytes": 109973,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/furniture-plan-art.generated-jmH3jfDa.js",
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
|
||||
"houseplan-assets/furniture-plan-art.generated-B7fH8oqt.js",
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/settings-de-BSeiuZ78.js",
|
||||
"sha256": "e097b530727671f7473cc5288ae1d71124fa51afb4e4b26e35114d10b2b18e8d",
|
||||
"rawBytes": 7248,
|
||||
"gzipBytes": 2825,
|
||||
"path": "houseplan-assets/settings-de-BjfMuSw8.js",
|
||||
"sha256": "62cda042ab64811c685f49f5aa338308e592f464f39afc1440de0031cdd97cf0",
|
||||
"rawBytes": 7378,
|
||||
"gzipBytes": 2878,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/settings-fr-Dqa7IIGC.js",
|
||||
"sha256": "b5597e3b95028ddd220c4bf7b2566455aa5e403d9a3ad2a55dc110cecdc6a33f",
|
||||
"rawBytes": 7483,
|
||||
"gzipBytes": 2786,
|
||||
"path": "houseplan-assets/settings-fr-BKCX3R6z.js",
|
||||
"sha256": "57246900bc095d6a84a2e7db9c96f7bb634aeb056957c431abbff760bed335db",
|
||||
"rawBytes": 7620,
|
||||
"gzipBytes": 2841,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/settings-ru-DTjpLI7z.js",
|
||||
"sha256": "20b462a7368412ce5c2a5ad378177c963894c6afe8b2a642ae8686477f5e5771",
|
||||
"rawBytes": 10128,
|
||||
"gzipBytes": 3271,
|
||||
"path": "houseplan-assets/settings-ru-D8xvcCw6.js",
|
||||
"sha256": "cf1089c5af8bffe24f153ae8553b8eeb14577862cb34b3ee51e2061b1585091c",
|
||||
"rawBytes": 10304,
|
||||
"gzipBytes": 3325,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-editor-BVcF5FC3.js",
|
||||
"sha256": "3b55c2b18035ea7d9861e5c1516d9c3a6443a6c042c2943dc2064ef2a51ed09e",
|
||||
"path": "houseplan-assets/summary-panel-editor-CwTXBm1f.js",
|
||||
"sha256": "6ccad563f28ed17d527b6e2a6efc167b696f478c7c23b114bf904ac4e695f566",
|
||||
"rawBytes": 14862,
|
||||
"gzipBytes": 3427,
|
||||
"gzipBytes": 3423,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-gtNYqb9U.js"
|
||||
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js",
|
||||
"houseplan-assets/summary-panel-runtime-loaded-jjP-5y3_.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-metrics-D9g7eyWm.js",
|
||||
"sha256": "ca53545e64213080520b779bd051d90e9a6e2d053656fdf2c2ca734b6b9e5982",
|
||||
"rawBytes": 2611,
|
||||
"gzipBytes": 1305,
|
||||
"path": "houseplan-assets/summary-panel-metrics-Bd81K-sc.js",
|
||||
"sha256": "20851cfa3b7d45452f558ac6ec231d6ba7a0a9b1459760580fa56ce7c5554a05",
|
||||
"rawBytes": 2701,
|
||||
"gzipBytes": 1344,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-gtNYqb9U.js",
|
||||
"sha256": "352dc342aca3222c76a922e010001ddc35e22d04c412093d5b294e9b67ab36ff",
|
||||
"rawBytes": 72956,
|
||||
"path": "houseplan-assets/summary-panel-runtime-loaded-jjP-5y3_.js",
|
||||
"sha256": "1ed46af8f90254099376b7107163e5ee6dfb282924409bd5a5fa40036660c2eb",
|
||||
"rawBytes": 72959,
|
||||
"gzipBytes": 20242,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/form-kit.styles-BJpA5NGj.js",
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
|
||||
"houseplan-assets/form-kit.styles-CCrtJjwP.js",
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/summary-panel-editor-BVcF5FC3.js",
|
||||
"houseplan-assets/summary-panel-metrics-D9g7eyWm.js"
|
||||
"houseplan-assets/summary-panel-editor-CwTXBm1f.js",
|
||||
"houseplan-assets/summary-panel-metrics-Bd81K-sc.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/support-de-DkCHsIJX.js",
|
||||
"sha256": "9bcaa6f84b2df92ccf9b26714a4132d9973efa598b93b25a966ffd290f80c00f",
|
||||
"path": "houseplan-assets/support-de-C9Oo6umO.js",
|
||||
"sha256": "aeab7835cf28821f302612a778f0687b05cb006c5371bbfeed15976ac0aba3c1",
|
||||
"rawBytes": 6167,
|
||||
"gzipBytes": 2396,
|
||||
"isEntry": false,
|
||||
@@ -339,17 +339,17 @@
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/support-fr-CF5yiuHl.js",
|
||||
"sha256": "967d72d645f5cd641e4a4d4fae7bf05a6b6a3752545a60c6981ae26c58c008b9",
|
||||
"path": "houseplan-assets/support-fr-CRCSYq8q.js",
|
||||
"sha256": "ff04381645035c564a6455ddfbfd81de57960e63977e4979ec1c04b4564f7885",
|
||||
"rawBytes": 6333,
|
||||
"gzipBytes": 2430,
|
||||
"gzipBytes": 2429,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/support-ru-Bs7OI6GX.js",
|
||||
"sha256": "63db0be43cdf00a66f24704ecba11224d6cba775fde57b8121a541be991360c3",
|
||||
"path": "houseplan-assets/support-ru-BcRlKfAd.js",
|
||||
"sha256": "0603d82d78e310ba8eaf26d4cc1b7ceeadf5cfabb40042199cb5b85b1089000c",
|
||||
"rawBytes": 8376,
|
||||
"gzipBytes": 2875,
|
||||
"isEntry": false,
|
||||
@@ -357,17 +357,17 @@
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/topology-de-BelPUGaW.js",
|
||||
"sha256": "6278b6aa3c8ddbb36ebc7ce6fb2b506ab2e6071814e5c78cd6f5241ee81b646b",
|
||||
"path": "houseplan-assets/topology-de-BjBB8qiK.js",
|
||||
"sha256": "df225ea5dc6b1886537cd7a0cec055530773ba31c94714a9cc199fd6fae2beb4",
|
||||
"rawBytes": 2698,
|
||||
"gzipBytes": 1336,
|
||||
"gzipBytes": 1334,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/topology-fr-FvLCzTYk.js",
|
||||
"sha256": "40ba15894ef18ef7566796a2cdb21bc8ad82f855c27556e4868d53f39e2fb146",
|
||||
"path": "houseplan-assets/topology-fr-DXFxoYd-.js",
|
||||
"sha256": "4c1013cb61db49df76e856c34aea59266e065350210a5d964537f8dd42cf6774",
|
||||
"rawBytes": 2718,
|
||||
"gzipBytes": 1354,
|
||||
"isEntry": false,
|
||||
@@ -375,62 +375,62 @@
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/topology-ru-DG2M1Xdq.js",
|
||||
"sha256": "e885da0310e6fe62f7b26a3fc8559d857bc03a5c1164146227b59a0532c6994f",
|
||||
"path": "houseplan-assets/topology-ru-Di7X71yN.js",
|
||||
"sha256": "daa7bcbbde34f363c8b114b2af050cc19264e85c7bcc6ba31a13e0b963f4f8ce",
|
||||
"rawBytes": 3669,
|
||||
"gzipBytes": 1614,
|
||||
"gzipBytes": 1615,
|
||||
"isEntry": false,
|
||||
"imports": [],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-CvQsfSze.js",
|
||||
"sha256": "e50d18bdc8333469cb81de65c7cbc1f7fc30f47820d90256835c20244e56c9fa",
|
||||
"path": "houseplan-assets/zigbee-topology-BbetEPvj.js",
|
||||
"sha256": "a204ece3a16a5af991ed3e6092e8ab2a3cd9d4d2bd79fdc1f056c504cdfa7798",
|
||||
"rawBytes": 10958,
|
||||
"gzipBytes": 4301,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
|
||||
"houseplan-assets/namespace-language-B2Cyshho.js"
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js",
|
||||
"houseplan-assets/namespace-language-CwX4Ljv1.js"
|
||||
],
|
||||
"dynamicImports": [
|
||||
"houseplan-assets/topology-de-BelPUGaW.js",
|
||||
"houseplan-assets/topology-fr-FvLCzTYk.js",
|
||||
"houseplan-assets/topology-ru-DG2M1Xdq.js"
|
||||
"houseplan-assets/topology-de-BjBB8qiK.js",
|
||||
"houseplan-assets/topology-fr-DXFxoYd-.js",
|
||||
"houseplan-assets/topology-ru-Di7X71yN.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-A7rG7kis.js",
|
||||
"sha256": "ff4fa95e61e5b87f04b9dbfdb5e4db560039b705ce85a78e0f88c5cc3fe752bf",
|
||||
"path": "houseplan-assets/zigbee-topology-runtime-sjaKOGGU.js",
|
||||
"sha256": "bd75de4445ae8f036fbf89763dead2ce464783b566a6860fd9603e5c9251099b",
|
||||
"rawBytes": 3983,
|
||||
"gzipBytes": 1788,
|
||||
"gzipBytes": 1789,
|
||||
"isEntry": false,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js",
|
||||
"houseplan-assets/namespace-language-B2Cyshho.js",
|
||||
"houseplan-assets/zigbee-topology-CvQsfSze.js"
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js",
|
||||
"houseplan-assets/namespace-language-CwX4Ljv1.js",
|
||||
"houseplan-assets/zigbee-topology-BbetEPvj.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-card.js",
|
||||
"sha256": "da2699ffc51cdc95977a044aafa7ef32645f69eb58311b994d2d578b8697c2c0",
|
||||
"sha256": "efc308c02a842ebafa05186a59d8ad2d734445445f1b9b746e1914706dca87cf",
|
||||
"rawBytes": 1205,
|
||||
"gzipBytes": 803,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
},
|
||||
{
|
||||
"path": "houseplan-panel.js",
|
||||
"sha256": "07baa17cfdd3db6f04aa5027c5796f623976bf6a4e5282255123474d79cd35b2",
|
||||
"sha256": "f820d606cf77d60252147bdf93075b2621d05e6cd3f014acdeb0c032b6b83d23",
|
||||
"rawBytes": 6165,
|
||||
"gzipBytes": 2326,
|
||||
"gzipBytes": 2324,
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"houseplan-assets/houseplan-card-BCYl6SkQ.js"
|
||||
"houseplan-assets/houseplan-card-Ddo22-SB.js"
|
||||
],
|
||||
"dynamicImports": []
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";import{aD as e,l as o,aG as t,ee as s,A as a,ef as i,eg as l,eh as n,E as r,c}from"./houseplan-card-BCYl6SkQ.js";class f extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,f={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},h=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";import{aD as e,l as o,aG as t,ey as s,A as a,ez as i,eA as l,eB as n,E as r,c}from"./houseplan-card-Ddo22-SB.js";class h extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,h={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},f=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._formData}
|
||||
.schema=${e}
|
||||
.computeLabel=${e=>f[e.name]||e.name}
|
||||
.computeLabel=${e=>h[e.name]||e.name}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`;return c`
|
||||
${d(h.slice(0,3))}
|
||||
${d(f.slice(0,3))}
|
||||
${_?c`<div class="default-floor-error" role="alert"
|
||||
style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere">
|
||||
${t(s,"editor.default_floor_missing",{id:_})}
|
||||
</div>`:a}
|
||||
${d(h.slice(3))}
|
||||
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}f.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",f);
|
||||
${d(f.slice(3))}
|
||||
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}h.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",h);
|
||||
@@ -1 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";import{ej as e,ek as a,eg as n}from"./houseplan-card-BCYl6SkQ.js";function o(n,o){return new e([{code:"en",dictionary:n},{code:"ru",loadDictionary:o.ru},{code:"de",loadDictionary:o.de},{code:"fr",loadDictionary:o.fr}],"5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051",console.warn,a)}function r(e){return function(e,a){return{state:n=>{const o=e.state(n);return"pending"!==o&&a.some(e=>"pending"===e.state(n))?"pending":o},dictionary:a=>e.dictionary(a),ensure:n=>Promise.all([e,...a].map(e=>e.ensure(n))).then(()=>{})}}(n,e)}export{o as n,r as s};
|
||||
@@ -0,0 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";import{eD as e,eE as n,eA as c}from"./houseplan-card-Ddo22-SB.js";function a(c,a){return new e([{code:"en",dictionary:c},{code:"ru",loadDictionary:a.ru},{code:"de",loadDictionary:a.de},{code:"fr",loadDictionary:a.fr}],"d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96",console.warn,n)}function d(e){return function(e,n){return{state:c=>{const a=e.state(c);return"pending"!==a&&n.some(e=>"pending"===e.state(c))?"pending":a},dictionary:n=>e.dictionary(n),ensure:c=>Promise.all([e,...n].map(e=>e.ensure(c))).then(()=>{})}}(c,e)}export{a as n,d as s};
|
||||
@@ -1,4 +1,4 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";import{c as a,A as e}from"./houseplan-card-BCYl6SkQ.js";import{s,n as t,a as l,m as r,b as o,c as i,d as u}from"./summary-panel-runtime-loaded-gtNYqb9U.js";import"./form-kit.styles-BJpA5NGj.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";import{c as a,A as e}from"./houseplan-card-Ddo22-SB.js";import{s,n as t,a as l,m as r,b as o,c as i,d as u}from"./summary-panel-runtime-loaded-jjP-5y3_.js";import"./form-kit.styles-CCrtJjwP.js";const m=m=>{const{host:c,dialog:d,problems:n,t:b}=m,y=n.find(a=>"error"===a.kind),$=d.localShow!==d.baseLocalShow||!d.localOnly&&!s(t(d.draft),d.base),p=$&&!d.busy&&!y,v=a=>n.find(e=>e.path===a),h=a=>a?b(`summary.problem.${a.code}`):"",k=()=>m.close(),g=a`<label class="summary-switch" for="summary-local-show">
|
||||
<span class="summary-switch-caption"><strong>${b("summary.show_local")}</strong>
|
||||
<small id="summary-local-show-hint">${b("summary.show_local_hint")}</small></span>
|
||||
<input id="summary-local-show" data-summary-local-show type="checkbox"
|
||||
@@ -0,0 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";import{eG as e,N as t,b2 as n,L as i,ae as o,ac as r,ca as s,eH as a,c9 as c,b1 as l,b0 as d,b3 as u,eI as f,eJ as m,cc as g,eK as v}from"./houseplan-card-Ddo22-SB.js";function y(e){if(!e.registry.authoritative)return null;const t=new Set,n=u(e.markers);for(const i of Object.values(e.registry.devices||{}))i?.id&&"service"!==i.entry_type&&!n.devices.has(i.id)&&i.area_id&&e.areaToSpace[i.area_id]&&t.add(i.id);for(const i of e.markers||[]){if(i.removed||"virtual"===i.binding)continue;const o=String(i.binding||"").indexOf(":");if(o<1)continue;const r=i.binding.slice(0,o),s=i.binding.slice(o+1),a="device"===r?e.registry.devices?.[s]:null,c="entity"===r?e.registry.entities?.[s]:null,l="device"===r?s:c?.device_id,d="device"===r?a?.area_id:c?.area_id||l&&e.registry.devices?.[l]?.area_id,u="string"==typeof i.room_id&&i.room_id.length>0&&null===i.area?"":i.area||d||"",f=u&&e.areaToSpace[u]||i.space||e.firstSpaceId;l&&e.registry.devices?.[l]&&e.spaceIds.has(f)&&(!n.devices.has(l)||"entity"===r&&n.liveEntities.has(s))&&t.add(l)}return t}function _(e,t){return e?.length?t?.length?v(e,t):e:t}function h(e,t){const n=l(e.rooms,t.walls,t.openCuts,[],i,t.cellCm,o,r);return{roomGeom:"ok"===n.status||"degraded-extra"===n.status?n.roomGeom:void 0,multiWallNodes:d(e.rooms,t.walls,t.openCuts,i,t.cellCm,o,r)}}function*p(l,d,u=h){try{let f=0;for(const m of d){const d=l.spaces.find(e=>String(e?.id)===m.id);if(!d)continue;const g=e(d,m),v=u(m,g);let y=null;for(const e of m.rooms){if(!e.id)continue;const a=t(e);if(!a)continue;const c=n(m.rooms,e.id,g.walls,g.openCuts,i,g.cellCm,o,r,v.roomGeom,v.multiWallNodes)||a;y=_(y,s(c,g.physicalBodies)),yield}const h=a(y||[],m.stairs);let p=h.next();for(;!p.done;)yield,p=h.next();y=p.value;const b=g.cellCm/o;f+=c(y)*b*b/1e4}return f}catch{return null}}function b(e,t,n=h){const i=p(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function S(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:g(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function C(e,t){const n=e?.states?.[t];if(!n)return null;const i=f(e,t);return i?m(i,String(n.attributes?.unit_of_measurement||"")):null}export{p as cleanFloorAreaSteps,y as representedHaDeviceIds,h as spaceWallGeometry,C as summaryEntityValue,S as summarySystemValue,b as totalCleanFloorAreaM2};
|
||||
@@ -1 +0,0 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";import{em as e,N as t,b2 as n,L as i,ae as o,ac as r,bY as s,bX as a,b1 as c,b0 as l,b3 as u,en as d,eo as f,b_ as m,ep as g}from"./houseplan-card-BCYl6SkQ.js";function v(e){if(!e.registry.authoritative)return null;const t=new Set,n=u(e.markers);for(const i of Object.values(e.registry.devices||{}))i?.id&&"service"!==i.entry_type&&!n.devices.has(i.id)&&i.area_id&&e.areaToSpace[i.area_id]&&t.add(i.id);for(const i of e.markers||[]){if(i.removed||"virtual"===i.binding)continue;const o=String(i.binding||"").indexOf(":");if(o<1)continue;const r=i.binding.slice(0,o),s=i.binding.slice(o+1),a="device"===r?e.registry.devices?.[s]:null,c="entity"===r?e.registry.entities?.[s]:null,l="device"===r?s:c?.device_id,u="device"===r?a?.area_id:c?.area_id||l&&e.registry.devices?.[l]?.area_id,d="string"==typeof i.room_id&&i.room_id.length>0&&null===i.area?"":i.area||u||"",f=d&&e.areaToSpace[d]||i.space||e.firstSpaceId;l&&e.registry.devices?.[l]&&e.spaceIds.has(f)&&(!n.devices.has(l)||"entity"===r&&n.liveEntities.has(s))&&t.add(l)}return t}function y(e,t){return e?.length?t?.length?g(e,t):e:t}function _(e,t){const n=c(e.rooms,t.walls,t.openCuts,[],i,t.cellCm,o,r);return{roomGeom:"ok"===n.status||"degraded-extra"===n.status?n.roomGeom:void 0,multiWallNodes:l(e.rooms,t.walls,t.openCuts,i,t.cellCm,o,r)}}function*p(c,l,u=_){try{let d=0;for(const f of l){const l=c.spaces.find(e=>String(e?.id)===f.id);if(!l)continue;const m=e(l,f),g=u(f,m);let v=null;for(const e of f.rooms){if(!e.id)continue;const a=t(e);if(!a)continue;const c=n(f.rooms,e.id,m.walls,m.openCuts,i,m.cellCm,o,r,g.roomGeom,g.multiWallNodes)||a;v=y(v,s(c,m.physicalBodies)),yield}const _=m.cellCm/o;d+=a(v)*_*_/1e4}return d}catch{return null}}function h(e,t,n=_){const i=p(e,t,n);let o=i.next();for(;!o.done;)o=i.next();return o.value}function b(e,t,n,i){if("system"!==e.type)return null;if("device_count"===e.key)return null===t.deviceCount?null:String(t.deviceCount);if("total_area"===e.key)return null===t.areaM2?null:m(t.areaM2,"mi"===n?.config?.unit_system?.length);try{return new Intl.DateTimeFormat(i||void 0,{dateStyle:"short",timeStyle:"short",timeZone:n?.config?.time_zone||void 0}).format(t.now)}catch{return t.now.toLocaleString()}}function S(e,t){const n=e?.states?.[t];if(!n)return null;const i=d(e,t);return i?f(i,String(n.attributes?.unit_of_measurement||"")):null}export{p as cleanFloorAreaSteps,v as representedHaDeviceIds,_ as spaceWallGeometry,S as summaryEntityValue,b as summarySystemValue,h as totalCleanFloorAreaM2};
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";var e={title:"Zigbee-Verbindungen",toggle:"Zigbee-Verbindungen beim Darüberfahren über ein Gerät anzeigen",hint:"Es werden nur beobachtete direkte Nachbarn angezeigt. Beim Darüberfahren werden keine Daten abgerufen.",help:"Zeigt, mit welchen Zigbee-Geräten jedes Gerät direkt verbunden ist. Die Linienfarbe ist die Verbindungsqualität (LQI) auf derselben Skala wie der LQI-Wert des Geräts: rot bei 40 und darunter, grün bei 180 und darüber. Eine gestrichelte Linie bedeutet, dass die Qualität nicht gemeldet wurde. Ein Pfeil zeigt auf das nächste Gerät auf dem Weg zum Koordinator: ein Pfeil führt hinaus, eingehende Pfeile sind die Geräte, die über dieses Gerät routen. Eine Linie ohne Pfeil ist ein Ersatznachbar. Das ist der Routenbaum, den House Plan bildet, nicht der Weg eines Pakets in diesem Moment: eine Beschriftung am Pfeilende bedeutet, dass das Ziel nicht auf diesem Plan liegt, und ein fehlender Pfeil, dass die Route unbekannt ist. Die Daten stammen aus dem letzten Laden und veralten; Verbindungen sind nur mit der Maus sichtbar.",help_aria:"Hilfe: Zigbee-Verbindungen",admin_only:"Topologiedaten sind nur für Home-Assistant-Administratoren verfügbar.",save_first:"Speichere diese Einstellung, bevor Topologiedaten geladen werden.",zha:"ZHA",zha_read:"ZHA-Daten lesen",zha_hint:"Liest den ZHA-Topologiecache; es wird kein Funkscan gestartet.",z2m:"Zigbee2MQTT",z2m_topics:"Basistopics (eines pro Zeile)",z2m_update:"Karte aktualisieren",z2m_warning:"Ein Netzwerkscan kann 10 Sekunden bis 2 Minuten dauern und Zigbee vorübergehend verlangsamen.",status_idle:"Daten nicht geladen",status_loading:"Wird geladen…",status_ready:"Empfangen: {time}",status_stale:"Empfangen: {time} · veraltet",status_partial:"Empfangen: {time} · einige Knoten fehlen",status_no_links:"Empfangen: {time} · keine beobachteten Verbindungen gefunden",error_permission:"Administratorrechte sind erforderlich.",error_unsupported:"Dieser Anbieter oder die Home-Assistant-API ist nicht verfügbar.",error_timeout:"Der Anbieter hat nicht rechtzeitig geantwortet.",error_invalid_topic:"Prüfe das Zigbee2MQTT-Basistopic.",error_invalid_payload:"Der Anbieter hat nicht unterstützte Topologiedaten geliefert.",error_provider:"Topologiedaten konnten nicht geladen werden.",remote_count:"+{n} in anderen Bereichen",route_device_not_on_plan:"Gerät ist nicht im Plan",route_coordinator_not_on_plan:"Koordinator ist nicht im Plan",route_other_space:"anderer Bereich"};const n="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";export{e as dictionary,n as fingerprint};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";var e={title:"Zigbee-Verbindungen",toggle:"Zigbee-Verbindungen beim Darüberfahren über ein Gerät anzeigen",hint:"Es werden nur beobachtete direkte Nachbarn angezeigt. Beim Darüberfahren werden keine Daten abgerufen.",help:"Zeigt, mit welchen Zigbee-Geräten jedes Gerät direkt verbunden ist. Die Linienfarbe ist die Verbindungsqualität (LQI) auf derselben Skala wie der LQI-Wert des Geräts: rot bei 40 und darunter, grün bei 180 und darüber. Eine gestrichelte Linie bedeutet, dass die Qualität nicht gemeldet wurde. Ein Pfeil zeigt auf das nächste Gerät auf dem Weg zum Koordinator: ein Pfeil führt hinaus, eingehende Pfeile sind die Geräte, die über dieses Gerät routen. Eine Linie ohne Pfeil ist ein Ersatznachbar. Das ist der Routenbaum, den House Plan bildet, nicht der Weg eines Pakets in diesem Moment: eine Beschriftung am Pfeilende bedeutet, dass das Ziel nicht auf diesem Plan liegt, und ein fehlender Pfeil, dass die Route unbekannt ist. Die Daten stammen aus dem letzten Laden und veralten; Verbindungen sind nur mit der Maus sichtbar.",help_aria:"Hilfe: Zigbee-Verbindungen",admin_only:"Topologiedaten sind nur für Home-Assistant-Administratoren verfügbar.",save_first:"Speichere diese Einstellung, bevor Topologiedaten geladen werden.",zha:"ZHA",zha_read:"ZHA-Daten lesen",zha_hint:"Liest den ZHA-Topologiecache; es wird kein Funkscan gestartet.",z2m:"Zigbee2MQTT",z2m_topics:"Basistopics (eines pro Zeile)",z2m_update:"Karte aktualisieren",z2m_warning:"Ein Netzwerkscan kann 10 Sekunden bis 2 Minuten dauern und Zigbee vorübergehend verlangsamen.",status_idle:"Daten nicht geladen",status_loading:"Wird geladen…",status_ready:"Empfangen: {time}",status_stale:"Empfangen: {time} · veraltet",status_partial:"Empfangen: {time} · einige Knoten fehlen",status_no_links:"Empfangen: {time} · keine beobachteten Verbindungen gefunden",error_permission:"Administratorrechte sind erforderlich.",error_unsupported:"Dieser Anbieter oder die Home-Assistant-API ist nicht verfügbar.",error_timeout:"Der Anbieter hat nicht rechtzeitig geantwortet.",error_invalid_topic:"Prüfe das Zigbee2MQTT-Basistopic.",error_invalid_payload:"Der Anbieter hat nicht unterstützte Topologiedaten geliefert.",error_provider:"Topologiedaten konnten nicht geladen werden.",remote_count:"+{n} in anderen Bereichen",route_device_not_on_plan:"Gerät ist nicht im Plan",route_coordinator_not_on_plan:"Koordinator ist nicht im Plan",route_other_space:"anderer Bereich"};const n="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";export{e as dictionary,n as fingerprint};
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";var e={title:"Liens Zigbee",toggle:"Afficher les liens Zigbee au survol d’un appareil",hint:"Seuls les voisins directs observés sont affichés. Le survol ne lance aucune requête.",help:"Montre avec quels appareils Zigbee chaque appareil communique directement. La couleur de la ligne est la qualité du lien (LQI) sur la même échelle que l'indicateur LQI de l'appareil : rouge à 40 et en dessous, vert à 180 et au-dessus. Une ligne pointillée signifie que la qualité n'a pas été communiquée. Une flèche pointe vers l'appareil suivant sur le chemin du coordinateur : une flèche part, les flèches entrantes sont les appareils qui passent par celui-ci. Une ligne sans flèche est un voisin de secours. C'est l'arbre de routes que construit House Plan, pas le trajet d'un paquet à cet instant : une étiquette au bout d'une flèche signifie que sa cible n'est pas sur ce plan, et l'absence de flèche que la route est inconnue. Les données proviennent du dernier chargement et vieillissent ; les liens ne sont visibles qu'à la souris.",help_aria:"Aide : liens Zigbee",admin_only:"Les données de topologie sont réservées aux administrateurs Home Assistant.",save_first:"Enregistrez ce réglage avant de charger les données de topologie.",zha:"ZHA",zha_read:"Lire les données ZHA",zha_hint:"Lit la topologie en cache de ZHA sans lancer d’analyse radio.",z2m:"Zigbee2MQTT",z2m_topics:"Topics de base (un par ligne)",z2m_update:"Actualiser la carte",z2m_warning:"L’analyse de la carte réseau peut durer de 10 secondes à 2 minutes et réduire temporairement la réactivité de Zigbee.",status_idle:"Données non chargées",status_loading:"Chargement…",status_ready:"Reçu : {time}",status_stale:"Reçu : {time} · obsolète",status_partial:"Reçu : {time} · certains nœuds sont omis",status_no_links:"Reçu : {time} · aucun lien observé trouvé",error_permission:"Les droits administrateur sont requis.",error_unsupported:"Ce fournisseur ou l’API Home Assistant n’est pas disponible.",error_timeout:"Le fournisseur n’a pas répondu à temps.",error_invalid_topic:"Vérifiez le topic de base Zigbee2MQTT.",error_invalid_payload:"Le fournisseur a renvoyé des données de topologie non prises en charge.",error_provider:"Impossible de charger les données de topologie.",remote_count:"+{n} dans d’autres espaces",route_device_not_on_plan:"appareil absent du plan",route_coordinator_not_on_plan:"coordinateur absent du plan",route_other_space:"autre espace"};const s="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";export{e as dictionary,s as fingerprint};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";var e={title:"Liens Zigbee",toggle:"Afficher les liens Zigbee au survol d’un appareil",hint:"Seuls les voisins directs observés sont affichés. Le survol ne lance aucune requête.",help:"Montre avec quels appareils Zigbee chaque appareil communique directement. La couleur de la ligne est la qualité du lien (LQI) sur la même échelle que l'indicateur LQI de l'appareil : rouge à 40 et en dessous, vert à 180 et au-dessus. Une ligne pointillée signifie que la qualité n'a pas été communiquée. Une flèche pointe vers l'appareil suivant sur le chemin du coordinateur : une flèche part, les flèches entrantes sont les appareils qui passent par celui-ci. Une ligne sans flèche est un voisin de secours. C'est l'arbre de routes que construit House Plan, pas le trajet d'un paquet à cet instant : une étiquette au bout d'une flèche signifie que sa cible n'est pas sur ce plan, et l'absence de flèche que la route est inconnue. Les données proviennent du dernier chargement et vieillissent ; les liens ne sont visibles qu'à la souris.",help_aria:"Aide : liens Zigbee",admin_only:"Les données de topologie sont réservées aux administrateurs Home Assistant.",save_first:"Enregistrez ce réglage avant de charger les données de topologie.",zha:"ZHA",zha_read:"Lire les données ZHA",zha_hint:"Lit la topologie en cache de ZHA sans lancer d’analyse radio.",z2m:"Zigbee2MQTT",z2m_topics:"Topics de base (un par ligne)",z2m_update:"Actualiser la carte",z2m_warning:"L’analyse de la carte réseau peut durer de 10 secondes à 2 minutes et réduire temporairement la réactivité de Zigbee.",status_idle:"Données non chargées",status_loading:"Chargement…",status_ready:"Reçu : {time}",status_stale:"Reçu : {time} · obsolète",status_partial:"Reçu : {time} · certains nœuds sont omis",status_no_links:"Reçu : {time} · aucun lien observé trouvé",error_permission:"Les droits administrateur sont requis.",error_unsupported:"Ce fournisseur ou l’API Home Assistant n’est pas disponible.",error_timeout:"Le fournisseur n’a pas répondu à temps.",error_invalid_topic:"Vérifiez le topic de base Zigbee2MQTT.",error_invalid_payload:"Le fournisseur a renvoyé des données de topologie non prises en charge.",error_provider:"Impossible de charger les données de topologie.",remote_count:"+{n} dans d’autres espaces",route_device_not_on_plan:"appareil absent du plan",route_coordinator_not_on_plan:"coordinateur absent du plan",route_other_space:"autre espace"};const s="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";export{e as dictionary,s as fingerprint};
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";var e={title:"Связи Zigbee",toggle:"Показывать связи Zigbee при наведении на устройство",hint:"Показываются только наблюдаемые прямые соседи. Наведение ничего не запрашивает.",help:"Показывает, с какими устройствами Zigbee каждое связано напрямую. Цвет линии — качество связи (LQI) по той же шкале, что у показателя LQI устройства: красный при 40 и ниже, зелёный при 180 и выше. Пунктир — качество не сообщено. Стрелка ведёт к следующему устройству по пути к координатору: исходящая одна, входящие — те, кто ходит через это устройство. Линия без стрелки — запасной сосед. Это дерево маршрутов, которое строит House Plan, а не путь пакета в эту секунду: подпись на конце стрелки значит, что цель не на этом плане, а отсутствие стрелки — что путь неизвестен. Данные из последней загрузки и устаревают, связи видны только при работе мышью.",help_aria:"Справка: связи Zigbee",admin_only:"Данные топологии доступны только администраторам Home Assistant.",save_first:"Сохраните настройку перед загрузкой данных топологии.",zha:"ZHA",zha_read:"Прочитать данные ZHA",zha_hint:"Читает сохранённую топологию ZHA и не запускает радио-сканирование.",z2m:"Zigbee2MQTT",z2m_topics:"Базовые топики (по одному в строке)",z2m_update:"Обновить карту",z2m_warning:"Сканирование карты сети занимает от 10 секунд до 2 минут и временно может снизить отзывчивость Zigbee.",status_idle:"Данные не загружены",status_loading:"Загрузка…",status_ready:"Получено: {time}",status_stale:"Получено: {time} · данные устарели",status_partial:"Получено: {time} · часть узлов не показана",status_no_links:"Получено: {time} · наблюдаемые связи не найдены",error_permission:"Нужны права администратора.",error_unsupported:"Провайдер или API Home Assistant недоступен.",error_timeout:"Провайдер не ответил вовремя.",error_invalid_topic:"Проверьте базовый топик Zigbee2MQTT.",error_invalid_payload:"Провайдер вернул неподдерживаемые данные топологии.",error_provider:"Не удалось загрузить данные топологии.",remote_count:"+{n} в других пространствах",route_device_not_on_plan:"устройства нет на плане",route_coordinator_not_on_plan:"координатора нет на плане",route_other_space:"другое пространство"};const a="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";export{e as dictionary,a as fingerprint};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";var e={title:"Связи Zigbee",toggle:"Показывать связи Zigbee при наведении на устройство",hint:"Показываются только наблюдаемые прямые соседи. Наведение ничего не запрашивает.",help:"Показывает, с какими устройствами Zigbee каждое связано напрямую. Цвет линии — качество связи (LQI) по той же шкале, что у показателя LQI устройства: красный при 40 и ниже, зелёный при 180 и выше. Пунктир — качество не сообщено. Стрелка ведёт к следующему устройству по пути к координатору: исходящая одна, входящие — те, кто ходит через это устройство. Линия без стрелки — запасной сосед. Это дерево маршрутов, которое строит House Plan, а не путь пакета в эту секунду: подпись на конце стрелки значит, что цель не на этом плане, а отсутствие стрелки — что путь неизвестен. Данные из последней загрузки и устаревают, связи видны только при работе мышью.",help_aria:"Справка: связи Zigbee",admin_only:"Данные топологии доступны только администраторам Home Assistant.",save_first:"Сохраните настройку перед загрузкой данных топологии.",zha:"ZHA",zha_read:"Прочитать данные ZHA",zha_hint:"Читает сохранённую топологию ZHA и не запускает радио-сканирование.",z2m:"Zigbee2MQTT",z2m_topics:"Базовые топики (по одному в строке)",z2m_update:"Обновить карту",z2m_warning:"Сканирование карты сети занимает от 10 секунд до 2 минут и временно может снизить отзывчивость Zigbee.",status_idle:"Данные не загружены",status_loading:"Загрузка…",status_ready:"Получено: {time}",status_stale:"Получено: {time} · данные устарели",status_partial:"Получено: {time} · часть узлов не показана",status_no_links:"Получено: {time} · наблюдаемые связи не найдены",error_permission:"Нужны права администратора.",error_unsupported:"Провайдер или API Home Assistant недоступен.",error_timeout:"Провайдер не ответил вовремя.",error_invalid_topic:"Проверьте базовый топик Zigbee2MQTT.",error_invalid_payload:"Провайдер вернул неподдерживаемые данные топологии.",error_provider:"Не удалось загрузить данные топологии.",remote_count:"+{n} в других пространствах",route_device_not_on_plan:"устройства нет на плане",route_coordinator_not_on_plan:"координатора нет на плане",route_other_space:"другое пространство"};const t="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";export{e as dictionary,t as fingerprint};
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";import{n as t,c as n}from"./zigbee-topology-CvQsfSze.js";import{aM as e}from"./houseplan-card-BCYl6SkQ.js";import"./namespace-language-B2Cyshho.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function m(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function g(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=n}),v=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&v.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&v.push(p),await m(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const b=await m(w,s-Date.now()),_=d(b)?.status;if(_&&"ok"!==_)throw a("provider");return n(b,l)}finally{for(const t of v)try{t()}catch{}}})}export{f as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";import{n as t,c as n}from"./zigbee-topology-BbetEPvj.js";import{aM as e}from"./houseplan-card-Ddo22-SB.js";import"./namespace-language-CwX4Ljv1.js";const o=new WeakMap;function i(t){const n=function(t){const n=t?.connection||t;return!n||"object"!=typeof n&&"function"!=typeof n?null:n}(t);if(!n)return null;let e=o.get(n);return e||(e={revision:0,topologies:[],states:{},listeners:new Set,inflight:new Map},o.set(n,e)),e}function r(t,n,e){t.states={...t.states,[n]:e},function(t){t.revision++;for(const n of t.listeners)n()}(t)}function s(t){const n=t?.code;if("permission"===n||"unsupported"===n||"timeout"===n||"invalid_topic"===n||"invalid_payload"===n)return n;const e=String(t?.message||"").toLowerCase();return e.includes("unauthor")||e.includes("permission")?"permission":e.includes("unknown_command")||e.includes("not found")?"unsupported":"provider"}function a(t){return Object.assign(new Error(t),{code:t})}function c(t,n,e){const o=t.inflight.get(n);if(o)return o;r(t,n,{phase:"loading"});const i=e().then(n=>{if(!n.nodes.length&&n.warnings.some(t=>"invalid_payload"===t.code))throw a("invalid_payload");!function(t,n){t.topologies=[...t.topologies.filter(t=>!(t.provider===n.provider&&t.instanceId===n.instanceId)),n],r(t,"zha"===n.provider?"zha":`z2m:${n.instanceId}`,{phase:"ready",obtainedAt:n.obtainedAt,partial:n.warnings.length>0})}(t,n)}).catch(e=>{r(t,n,{phase:"error",error:s(e)})}).finally(()=>t.inflight.delete(n));return t.inflight.set(n,i),i}function l(t){const n=i(t);return n?{revision:n.revision,topologies:n.topologies,states:n.states}:{revision:0,topologies:[],states:{}}}function u(t,n){const e=i(t);return e?(e.listeners.add(n),()=>e.listeners.delete(n)):()=>{}}function p(t){if(!0!==t?.user?.is_admin)throw a("permission")}function f(n){const e=i(n);return e?c(e,"zha",async()=>{if(p(n),"function"!=typeof n?.callWS)throw a("unsupported");return t(await n.callWS({type:"zha/devices"}))}):Promise.resolve()}function d(t){return null!==t&&"object"==typeof t?t:null}function h(t){const n=d(t),e=n?.payload??t;if("string"!=typeof e)return e;try{return JSON.parse(e)}catch{return null}}async function m(t,n){let e;try{return await Promise.race([t,new Promise((t,o)=>{e=globalThis.setTimeout(()=>o(a("timeout")),Math.max(1,n))})])}finally{void 0!==e&&globalThis.clearTimeout(e)}}function g(t,o,r=15e4){const s=i(t);if(!s)return Promise.resolve();const l=e(o);return c(s,`z2m:${l||String(o)}`,async()=>{if(p(t),!l)throw a("invalid_topic");const e=t.connection,o=e?.subscribeMessage;if("function"!=typeof o||"function"!=typeof t?.callService)throw a("unsupported");const i=function(){const t=globalThis.crypto;return"function"==typeof t?.randomUUID?`houseplan-${t.randomUUID()}`:`houseplan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}(),s=Date.now()+Math.max(1,r);let c=!1,u=null,f=null,g=null;const y=new Promise(t=>{u=t}),w=new Promise((t,n)=>{f=t,g=n}),b=[];try{const r=await o.call(e,t=>{!0===d(t)?.retain&&h(t)&&u?.()},{type:"mqtt/subscribe",topic:`${l}/bridge/info`});"function"==typeof r&&b.push(r);const p=await o.call(e,t=>{if(!0===d(t)?.retain)return;const n=h(t);null!==n?n&&function(t){const n=d(t),e=n?.transaction??d(n?.data)?.transaction;return"string"==typeof e||"number"==typeof e?String(e):null}(n)===i&&f?.(n):c&&g?.(a("invalid_payload"))},{type:"mqtt/subscribe",topic:`${l}/bridge/response/networkmap`});"function"==typeof p&&b.push(p),await m(y,Math.min(4e3,Math.max(1,s-Date.now()))),c=!0,await t.callService("mqtt","publish",{topic:`${l}/bridge/request/networkmap`,payload:JSON.stringify({type:"raw",routes:!1,transaction:i}),qos:0,retain:!1});const v=await m(w,s-Date.now()),_=d(v)?.status;if(_&&"ok"!==_)throw a("provider");return n(v,l)}finally{for(const t of b)try{t()}catch{}}})}export{f as readZhaTopology,g as refreshZ2mTopology,u as subscribeZigbeeTopology,l as zigbeeTopologyRuntimeSnapshot};
|
||||
@@ -1 +1 @@
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5c8df099f4dfec9208859aaf7e0462e397d6695e1e9cc1f21a972a698af9e051";try{await import("./houseplan-assets/houseplan-card-BCYl6SkQ.js")}catch(e){if(!customElements.get("houseplan-card")){const l=String(navigator.language||"en").toLowerCase();const m=l.startsWith("ru")?"House Plan обновился — перезагрузите страницу (Ctrl+F5).":l.startsWith("de")?"House Plan wurde aktualisiert — bitte laden Sie die Seite neu (Strg+F5).":l.startsWith("fr")?"House Plan a été mis à jour — veuillez recharger la page (Ctrl+F5).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale houseplan-card.js: the implementation chunk is unavailable",e)}
|
||||
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="d06c84d556e989bcf1e9fa8c0eb28b72de2cbf0fe7a8c35051d3497e78d9cf96";try{await import("./houseplan-assets/houseplan-card-Ddo22-SB.js")}catch(e){if(!customElements.get("houseplan-card")){const l=String(navigator.language||"en").toLowerCase();const m=l.startsWith("ru")?"House Plan обновился — перезагрузите страницу (Ctrl+F5).":l.startsWith("de")?"House Plan wurde aktualisiert — bitte laden Sie die Seite neu (Strg+F5).":l.startsWith("fr")?"House Plan a été mis à jour — veuillez recharger la page (Ctrl+F5).":"House Plan was updated — please reload the page (Ctrl+F5).";customElements.define("houseplan-card",class extends HTMLElement{setConfig(){}getCardSize(){return 1}connectedCallback(){this.style.cssText="display:block;box-sizing:border-box;padding:16px;border:1px solid var(--divider-color,#e0e0e0);border-radius:var(--ha-card-border-radius,12px);background:var(--card-background-color,#fff);color:var(--primary-text-color,#212121);font:14px/1.4 var(--paper-font-body1_-_font-family,sans-serif)";this.textContent=m}})}console.error("[houseplan] stale houseplan-card.js: the implementation chunk is unavailable",e)}
|
||||
|
||||
@@ -73,7 +73,7 @@ _LIVE_TEXT_ENTITY = re.compile(r"^[a-z0-9_]+\.[a-z0-9_]+$")
|
||||
_LIVE_TEXT_ATTRIBUTE = re.compile(r"^[a-zA-Z0-9_.-]+$")
|
||||
_PLAN_ONLY_DASH = "—"
|
||||
_IMPORT_ID_NAMESPACES = {
|
||||
"space", "room", "marker", "partition", "wall", "opening", "decor", "column",
|
||||
"space", "room", "marker", "partition", "wall", "opening", "decor", "column", "stair",
|
||||
}
|
||||
_MAX_IMPORT_LINEAGE_DEPTH = 16
|
||||
_REPORT_EXAMPLE_LIMIT = 24
|
||||
@@ -294,6 +294,14 @@ def _project_plan_only_space(space: dict[str, Any]) -> dict[str, Any]:
|
||||
projected["decor"] = [
|
||||
_project_plan_only_decor(shape) for shape in space.get("decor") or []
|
||||
]
|
||||
if "stairs" in space:
|
||||
projected["stairs"] = [
|
||||
_pick_fields(stair, (
|
||||
"id", "kind", "x", "y", "angle", "direction",
|
||||
"target_space_id", "length", "width", "radius",
|
||||
))
|
||||
for stair in space.get("stairs") or []
|
||||
]
|
||||
return projected
|
||||
|
||||
|
||||
@@ -742,6 +750,7 @@ def _counts(config: dict[str, Any], layout: dict[str, Any]) -> dict[str, int]:
|
||||
"markers": len(config.get("markers") or []),
|
||||
"openings": sum(len(sp.get("openings") or []) for sp in spaces),
|
||||
"decor": sum(len(sp.get("decor") or []) for sp in spaces),
|
||||
"stairs": sum(len(sp.get("stairs") or []) for sp in spaces),
|
||||
"layout": len(layout),
|
||||
}
|
||||
|
||||
@@ -1132,6 +1141,14 @@ def _repair_target_space_refs(
|
||||
|
||||
for space in spaces:
|
||||
space_id = str(space.get("id", "?"))
|
||||
for stair in space.get("stairs") or []:
|
||||
target = stair.get("target_space_id") if isinstance(stair, dict) else None
|
||||
mapped = replace(
|
||||
f"{space_id}:{stair.get('id', '?')}", "stair.target_space_id",
|
||||
target, resolve_space,
|
||||
)
|
||||
if mapped is not None:
|
||||
stair["target_space_id"] = mapped
|
||||
for room in space.get("rooms") or []:
|
||||
room_id = str(room.get("id", "?"))
|
||||
values = room.get("open_to")
|
||||
@@ -1322,7 +1339,7 @@ def build_space_merge(
|
||||
(sp,), sp.get("rooms") or [],
|
||||
sp.get("partitions") or [], sp.get("wall_segments") or [],
|
||||
sp.get("wall_columns") or [],
|
||||
sp.get("openings") or [], sp.get("decor") or [],
|
||||
sp.get("openings") or [], sp.get("decor") or [], sp.get("stairs") or [],
|
||||
)
|
||||
for item in collection
|
||||
if isinstance(item, dict)
|
||||
@@ -1338,7 +1355,7 @@ def build_space_merge(
|
||||
(incoming_space,), incoming_space.get("rooms") or [],
|
||||
incoming_space.get("partitions") or [], incoming_space.get("wall_segments") or [],
|
||||
incoming_space.get("wall_columns") or [],
|
||||
incoming_space.get("openings") or [], incoming_space.get("decor") or [],
|
||||
incoming_space.get("openings") or [], incoming_space.get("decor") or [], incoming_space.get("stairs") or [],
|
||||
)
|
||||
for item in collection
|
||||
if isinstance(item, dict)
|
||||
@@ -1362,6 +1379,7 @@ def build_space_merge(
|
||||
("rooms", "room"), ("partitions", "partition"),
|
||||
("wall_segments", "wall"),
|
||||
("wall_columns", "column"), ("openings", "opening"), ("decor", "decor"),
|
||||
("stairs", "stair"),
|
||||
):
|
||||
for item in space.get(collection) or []:
|
||||
if isinstance(item, dict) and item.get("id") is not None:
|
||||
@@ -1405,6 +1423,16 @@ def build_space_merge(
|
||||
reference_report, "incoming", "opening.host",
|
||||
str(opening.get("id", "?")), old_host_id,
|
||||
)
|
||||
# A one-space export cannot carry a trustworthy external floor target.
|
||||
# Self-links are invalid too because the imported source gets a new id.
|
||||
for stair in space.get("stairs") or []:
|
||||
target = stair.get("target_space_id") if isinstance(stair, dict) else None
|
||||
if target is not None:
|
||||
stair["target_space_id"] = None
|
||||
_report_remap(
|
||||
reference_report, "incoming", "stair.target_space_id",
|
||||
str(stair.get("id", "?")), str(target),
|
||||
)
|
||||
space["id"] = new_space_id
|
||||
space["title"] = _unique_title(
|
||||
str(space.get("title") or old_space_id), current_config.get("spaces") or []
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
|
||||
"requirements": [],
|
||||
"single_config_entry": true,
|
||||
"version": "1.78.0-beta.2"
|
||||
"version": "1.78.0-beta.5"
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ def create_data(hass: HomeAssistant) -> HouseplanData:
|
||||
)
|
||||
from .virtual_lights import VirtualLightController
|
||||
|
||||
data.virtual_lights = VirtualLightController(data.virtual_light_store)
|
||||
data.virtual_lights = VirtualLightController(hass, data.virtual_light_store)
|
||||
return data
|
||||
|
||||
|
||||
|
||||
@@ -155,6 +155,9 @@ def _global_settings(value: object) -> dict[str, Any]:
|
||||
show_room_tooltip = value.get("show_room_tooltip")
|
||||
if isinstance(show_room_tooltip, bool):
|
||||
out["show_room_tooltip"] = show_room_tooltip
|
||||
volumetric_view = value.get("volumetric_view")
|
||||
if isinstance(volumetric_view, bool):
|
||||
out["volumetric_view"] = volumetric_view
|
||||
fill_colors = value.get("fill_colors")
|
||||
if isinstance(fill_colors, dict):
|
||||
palette = {
|
||||
@@ -371,6 +374,17 @@ def _project_space(ids: _Pseudonyms, space: dict[str, Any], index: int) -> dict[
|
||||
out["decor"] = [
|
||||
_project_decor(ids, item) for item in space.get("decor") or [] if isinstance(item, dict)
|
||||
]
|
||||
out["stairs"] = []
|
||||
for item in space.get("stairs") or []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
projected = {"id": ids.get("stair", item.get("id"))}
|
||||
projected.update(_copy_keys(item, (
|
||||
"kind", "x", "y", "angle", "direction", "length", "width", "radius",
|
||||
)))
|
||||
if item.get("target_space_id"):
|
||||
projected["target_space_id"] = ids.get("space", item.get("target_space_id"))
|
||||
out["stairs"].append(projected)
|
||||
out["open_spans"] = [
|
||||
{"a": _point(item.get("a")), "b": _point(item.get("b"))}
|
||||
for item in space.get("open_spans") or [] if isinstance(item, dict)
|
||||
@@ -433,6 +447,7 @@ def _summary(config: object, layout: object) -> dict[str, Any]:
|
||||
"columns": sum(len(space.get("wall_columns") or []) for space in spaces if isinstance(space, dict)),
|
||||
"openings": dict(sorted(kinds.items())),
|
||||
"decor": dict(sorted(decor_kinds.items())),
|
||||
"stairs": sum(len(space.get("stairs") or []) for space in spaces if isinstance(space, dict)),
|
||||
"markers": {
|
||||
"total": len(markers),
|
||||
"lifecycle": dict(sorted(lifecycles.items())),
|
||||
|
||||
@@ -31,6 +31,7 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
|
||||
"rooms": sum(len(s.get("rooms", [])) for s in config.get("spaces", [])),
|
||||
"partitions": sum(len(s.get("partitions", [])) for s in config.get("spaces", [])),
|
||||
"wall_columns": sum(len(s.get("wall_columns", [])) for s in config.get("spaces", [])),
|
||||
"stairs": sum(len(s.get("stairs", [])) for s in config.get("spaces", [])),
|
||||
"markers": len(config.get("markers", [])),
|
||||
"layout_entries": len(layout_raw.get("layout", {})),
|
||||
}
|
||||
|
||||
@@ -1198,6 +1198,7 @@ MAX_ROOMS = 400
|
||||
MAX_MARKERS = 2000
|
||||
MAX_OPENINGS = 500
|
||||
MAX_DECOR = 1000
|
||||
MAX_STAIRS = 250
|
||||
# v8 atomises room boundaries. The 2 MiB wire cap is the practical bound; this
|
||||
# structural cap mirrors MAX_ROOMS * MAX_POLY_POINTS without depending on the
|
||||
# later constant declaration.
|
||||
@@ -1532,6 +1533,57 @@ DECOR_SCHEMA = vol.Any(
|
||||
)
|
||||
|
||||
|
||||
def _strict_stair(value: dict) -> dict:
|
||||
"""A discriminated stair record carries dimensions for exactly one kind."""
|
||||
if value["kind"] == "straight":
|
||||
if "radius" in value:
|
||||
raise vol.Invalid("radius is allowed only for spiral stairs")
|
||||
if value["direction"] not in ("forward", "backward"):
|
||||
raise vol.Invalid("straight stair direction must be forward or backward")
|
||||
else:
|
||||
if "length" in value or "width" in value:
|
||||
raise vol.Invalid("length and width are allowed only for straight stairs")
|
||||
if value["direction"] not in ("clockwise", "counterclockwise"):
|
||||
raise vol.Invalid("spiral stair direction must be clockwise or counterclockwise")
|
||||
return value
|
||||
|
||||
|
||||
def _stair_dimensions(value: dict) -> dict:
|
||||
"""Require the dimension fields owned by the selected stair kind."""
|
||||
complete = (
|
||||
value["kind"] == "straight" and "length" in value and "width" in value
|
||||
or value["kind"] == "spiral" and "radius" in value
|
||||
)
|
||||
if not complete:
|
||||
raise vol.Invalid("stair kind requires its dimensions")
|
||||
return value
|
||||
|
||||
|
||||
STAIR_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required("id"): vol.All(str, vol.Length(min=1, max=64)),
|
||||
vol.Required("kind"): vol.In(["straight", "spiral"]),
|
||||
vol.Required("x"): _NORM,
|
||||
vol.Required("y"): _NORM,
|
||||
vol.Required("angle"): vol.All(_finite, vol.Range(min=-360.0, max=360.0)),
|
||||
vol.Required("direction"): vol.In([
|
||||
"forward", "backward", "clockwise", "counterclockwise",
|
||||
]),
|
||||
vol.Optional("target_space_id"): vol.Any(
|
||||
None, vol.All(str, vol.Match(SPACE_ID_RE.pattern))
|
||||
),
|
||||
vol.Optional("length"): _FURN_SIZE,
|
||||
vol.Optional("width"): _FURN_SIZE,
|
||||
vol.Optional("radius"): _FURN_SIZE,
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
),
|
||||
_stair_dimensions,
|
||||
_strict_stair,
|
||||
)
|
||||
|
||||
|
||||
def _wall_endpoints_pair(entry: dict) -> dict:
|
||||
"""Exact wall endpoints are useful only as a complete a/b pair."""
|
||||
if ("a" in entry) != ("b" in entry):
|
||||
@@ -1673,7 +1725,7 @@ OPENING_HOST_SCHEMA = vol.Any(PARTITION_OPENING_HOST_SCHEMA, WALL_OPENING_HOST_S
|
||||
def _space_geometry_invariants(value: dict) -> dict:
|
||||
"""All stored geometry shares ids; draft segments also have a space cap."""
|
||||
seen: set[str] = set()
|
||||
for key in ("rooms", "openings", "decor", "room_drafts", "partitions", "wall_columns", "wall_segments"):
|
||||
for key in ("rooms", "openings", "decor", "stairs", "room_drafts", "partitions", "wall_columns", "wall_segments"):
|
||||
for item in value.get(key, []):
|
||||
item_id = item.get("id")
|
||||
if not item_id:
|
||||
@@ -1768,6 +1820,7 @@ SPACE_SCHEMA = vol.All(vol.Schema(
|
||||
vol.Required("view_box"): _view_box,
|
||||
vol.Required("rooms"): vol.All([ROOM_SCHEMA], vol.Length(max=MAX_ROOMS)),
|
||||
vol.Optional("decor"): vol.All([DECOR_SCHEMA], vol.Length(max=MAX_DECOR)),
|
||||
vol.Optional("stairs"): vol.All([STAIR_SCHEMA], vol.Length(max=MAX_STAIRS)),
|
||||
vol.Optional("openings"): vol.All([
|
||||
vol.Schema(
|
||||
{
|
||||
@@ -2254,6 +2307,8 @@ CONFIG_SCHEMA = vol.All(
|
||||
vol.Optional("sun_rays"): bool,
|
||||
vol.Optional("sun_ray_origin"): _SUN_RAY_ORIGIN,
|
||||
vol.Optional("show_room_tooltip"): bool,
|
||||
# #649: installation-wide 2.5D View; absent/false keeps Flat.
|
||||
vol.Optional("volumetric_view"): bool,
|
||||
vol.Optional("summary_panel"): SUMMARY_PANEL_WIRE_SCHEMA,
|
||||
vol.Optional("radar"): vol.Schema({}, extra=vol.ALLOW_EXTRA),
|
||||
# Removed from the UI/runtime in 2026-08-08. Keep accepting the
|
||||
|
||||
@@ -7,6 +7,8 @@ import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .store import HouseplanStore
|
||||
|
||||
|
||||
@@ -81,12 +83,14 @@ def _snapshot_payload(
|
||||
class VirtualLightController:
|
||||
"""Runtime cache with coalesced durable writes for rapid toggles."""
|
||||
|
||||
def __init__(self, store: HouseplanStore) -> None:
|
||||
def __init__(self, hass: HomeAssistant, store: HouseplanStore) -> None:
|
||||
self.hass = hass
|
||||
self.store = store
|
||||
self._state: dict[str, Any] | None = None
|
||||
self._dirty = False
|
||||
self._save_task: asyncio.Task[None] | None = None
|
||||
self._flush_event = asyncio.Event()
|
||||
self._flush_lock = asyncio.Lock()
|
||||
|
||||
async def async_snapshot(
|
||||
self, config: dict[str, Any], config_rev: int,
|
||||
@@ -127,7 +131,7 @@ class VirtualLightController:
|
||||
|
||||
def _schedule_save(self) -> None:
|
||||
if self._save_task is None or self._save_task.done():
|
||||
self._save_task = asyncio.create_task(self._delayed_save())
|
||||
self._save_task = self.hass.async_create_task(self._delayed_save())
|
||||
|
||||
async def _delayed_save(self) -> None:
|
||||
try:
|
||||
@@ -156,16 +160,17 @@ class VirtualLightController:
|
||||
|
||||
async def async_flush(self) -> None:
|
||||
"""Persist the latest state before config transitions or unload."""
|
||||
task = self._save_task
|
||||
if task is not None:
|
||||
self._flush_event.set()
|
||||
await task
|
||||
if self._dirty and self._state is not None:
|
||||
payload = copy.deepcopy(self._state)
|
||||
await self.store.async_save(payload)
|
||||
self._dirty = False
|
||||
if not self._dirty:
|
||||
self._flush_event.clear()
|
||||
async with self._flush_lock:
|
||||
task = self._save_task
|
||||
if task is not None:
|
||||
self._flush_event.set()
|
||||
await task
|
||||
if self._dirty and self._state is not None:
|
||||
payload = copy.deepcopy(self._state)
|
||||
await self.store.async_save(payload)
|
||||
self._dirty = False
|
||||
if not self._dirty:
|
||||
self._flush_event.clear()
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Forget cache after an external reconciliation wrote the store."""
|
||||
|
||||
@@ -439,7 +439,7 @@ def _thickness(space: dict[str, Any], atom: dict[str, Any], previous: dict[str,
|
||||
|
||||
def _non_catalog_ids(space: dict[str, Any]) -> set[str]:
|
||||
result: set[str] = set()
|
||||
for name in ("rooms", "openings", "decor", "room_drafts", "partitions", "wall_columns"):
|
||||
for name in ("rooms", "openings", "decor", "stairs", "room_drafts", "partitions", "wall_columns"):
|
||||
for item in space.get(name) or []:
|
||||
if isinstance(item, dict) and isinstance(item.get("id"), str) and item["id"]:
|
||||
result.add(item["id"])
|
||||
@@ -661,7 +661,7 @@ def _migrate_room_drafts_to_partitions(space: dict[str, Any]) -> tuple[int, int]
|
||||
return 0, 0
|
||||
used = {
|
||||
str(item["id"])
|
||||
for name in ("rooms", "openings", "decor", "partitions",
|
||||
for name in ("rooms", "openings", "decor", "stairs", "partitions",
|
||||
"wall_columns", "wall_segments")
|
||||
for item in space.get(name) or []
|
||||
if isinstance(item, dict) and isinstance(item.get("id"), str) and item["id"]
|
||||
|
||||
@@ -1848,6 +1848,10 @@ def _space_delete_candidate(
|
||||
item for item in candidate_config.get("spaces") or []
|
||||
if item.get("id") != space_id
|
||||
]
|
||||
for remaining_space in candidate_config["spaces"]:
|
||||
for stair in remaining_space.get("stairs") or []:
|
||||
if isinstance(stair, dict) and stair.get("target_space_id") == space_id:
|
||||
stair["target_space_id"] = None
|
||||
for marker in candidate_config.get("markers") or []:
|
||||
marker_id = str(marker.get("id")) if marker.get("id") is not None else None
|
||||
marker_position = candidate_layout.get(marker_id) if marker_id is not None else None
|
||||
|
||||
@@ -40,6 +40,7 @@ const requiresIsoStructuralBuildCounter = requiresIsometric
|
||||
const requiresPlanSnap = planSnap && existsSync(resolve(targetRoot, 'src/plan-snap-overlay.ts'));
|
||||
const requiresWallFace = planSnap && existsSync(resolve(targetRoot, 'src/wall-face-graph.ts'));
|
||||
const requiresInteraction = interaction && existsSync(resolve(targetRoot, 'src/live-viewport.ts'));
|
||||
const requiresStairs = existsSync(resolve(targetRoot, 'src/stairs.ts'));
|
||||
const fixture = stage3Dense ? makeIsometricStage3DenseFixture() : makeLargeHouseFixture();
|
||||
const fixtureCounts = stage3Dense ? fixture.counts : LARGE_HOUSE_COUNTS;
|
||||
const sourceSha = (() => {
|
||||
@@ -100,7 +101,7 @@ try {
|
||||
const row = await page.evaluate(async ({
|
||||
fixture, sample, cardContract, isometric, requiresIsometric, planSnap, requiresPlanSnap,
|
||||
requiresWallFace, interaction, requiresInteraction, stage3Dense, requireStage3,
|
||||
requiresIsoStructuralBuildCounter, profile,
|
||||
requiresIsoStructuralBuildCounter, requiresStairs, profile,
|
||||
}) => {
|
||||
const frame = () => new Promise((done) => requestAnimationFrame(() => requestAnimationFrame(done)));
|
||||
const until = async (predicate, timeout = 10000) => {
|
||||
@@ -142,7 +143,7 @@ try {
|
||||
longTasks: await longTasks.stop(),
|
||||
};
|
||||
};
|
||||
// Current candidates split the experimental Iso runtime out of the initial graph. Comparison
|
||||
// Current candidates split the lazy 2.5D runtime out of the initial graph. Comparison
|
||||
// bundles remain valid: their older monolithic renderer has no preload
|
||||
// hook and is already available synchronously.
|
||||
const ensureIsoRuntime = async (card) => {
|
||||
@@ -181,7 +182,7 @@ try {
|
||||
const overlaySelectors = {
|
||||
device: '[data-hp="device"]',
|
||||
'room-label': '[data-hp="room-label"]',
|
||||
'opening-lock': '.oplock',
|
||||
'opening-lock': '.oplock:not(.iso-tile-shadow)',
|
||||
};
|
||||
const totalRootsByKind = Object.fromEntries(Object.entries(overlaySelectors)
|
||||
.map(([kind, selector]) => [kind, card.renderRoot.querySelectorAll(selector).length]));
|
||||
@@ -291,6 +292,9 @@ try {
|
||||
fixture.config.spaces.map((space) => [space.id, 'iso']),
|
||||
)));
|
||||
history.replaceState(null, '', '?hp_alpha=1');
|
||||
// #649: current bundles read the installation setting; the alpha
|
||||
// storage above stays for comparison bundles before it.
|
||||
fixture.config.settings = { ...(fixture.config.settings || {}), volumetric_view: true };
|
||||
} else history.replaceState(null, '', location.pathname);
|
||||
const host = document.getElementById('host');
|
||||
const card = document.createElement('houseplan-card');
|
||||
@@ -386,7 +390,7 @@ try {
|
||||
throw new Error('large-house editor runtime did not preload');
|
||||
if (isometric) await ensureIsoRuntime(card);
|
||||
window.__hpAssertCardContract(card, cardContract);
|
||||
if (requiresIsometric && (typeof card._setProjection !== 'function'
|
||||
if (requiresIsometric && (typeof card._effectiveProjection !== 'function'
|
||||
|| !(card._isoGeometryCache instanceof Map))) {
|
||||
throw new Error('large-house-isometric-v1 candidate has no renderer contract');
|
||||
}
|
||||
@@ -397,6 +401,10 @@ try {
|
||||
await until(() => card._booting === false);
|
||||
if (interaction && '_bootSoft' in card) await until(() => card._bootSoft === false);
|
||||
await frame();
|
||||
if (requiresStairs
|
||||
&& card.renderRoot.querySelectorAll('[data-hp="stair"]').length !== fixture.counts.stairs) {
|
||||
throw new Error(`${profile} did not render the bounded maximum stair collection`);
|
||||
}
|
||||
const firstStableRenderMs = Number((performance.now() - loadStarted).toFixed(2));
|
||||
const bootDiag = {
|
||||
updates: card.__diag.updates,
|
||||
@@ -445,11 +453,20 @@ try {
|
||||
}
|
||||
const viewToggle = isometric ? await duration(async () => {
|
||||
if (typeof card._setProjection === 'function') {
|
||||
// Comparison bundles before #649: the per-device alpha preference.
|
||||
card._setProjection('flat');
|
||||
await card.updateComplete;
|
||||
card._setProjection('iso');
|
||||
await ensureIsoRuntime(card);
|
||||
await card.updateComplete;
|
||||
} else if (typeof card._syncVolumetricSetting === 'function') {
|
||||
// #649: the installation-wide General settings switch, as a save applies it.
|
||||
const cfg = card._serverCfg;
|
||||
card._serverCfg = { ...cfg, settings: { ...(cfg.settings || {}), volumetric_view: false } };
|
||||
await card.updateComplete;
|
||||
card._serverCfg = { ...cfg, settings: { ...(cfg.settings || {}), volumetric_view: true } };
|
||||
await ensureIsoRuntime(card);
|
||||
await card.updateComplete;
|
||||
} else {
|
||||
// Comparison SHAs before #89 intentionally ignore the Labs operation.
|
||||
card.requestUpdate();
|
||||
@@ -1207,7 +1224,7 @@ try {
|
||||
fixture, sample: measuredSample, cardContract: LARGE_HOUSE_CARD_CONTRACT,
|
||||
isometric, requiresIsometric, planSnap, requiresPlanSnap, requiresWallFace,
|
||||
interaction, requiresInteraction, stage3Dense, requireStage3,
|
||||
requiresIsoStructuralBuildCounter, profile,
|
||||
requiresIsoStructuralBuildCounter, requiresStairs, profile,
|
||||
});
|
||||
// #520: диагностика печатается в лог прогона и в запись не попадает.
|
||||
const { bootDiag, ...measured } = row;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// #649 AC12: product frames for docs/design/649-25d-stage6/ACCEPTANCE.md.
|
||||
// These scenarios are canonical golden entries (#673). This focused command
|
||||
// remains a diagnostic helper for rebuilding the side-by-side design document;
|
||||
// baseline capture and acceptance still use the complete golden matrix (#455).
|
||||
//
|
||||
// npm run build && npm run bundle:sync
|
||||
// node demo/capture_stage6_acceptance_649.mjs [out-dir]
|
||||
import { mkdirSync } from 'node:fs';
|
||||
import { launch } from './serve.mjs';
|
||||
import { goldenClip, prepareGoldenScenario } from './golden/harness.mjs';
|
||||
import { STAGE6_ACCEPTANCE_SCENARIOS } from './golden/matrix.mjs';
|
||||
|
||||
const out = process.argv[2] || 'artifacts/stage6-acceptance';
|
||||
mkdirSync(out, { recursive: true });
|
||||
const { page, browser } = await launch({ width: 1000, height: 900 }, 1);
|
||||
for (const scenario of STAGE6_ACCEPTANCE_SCENARIOS) {
|
||||
await prepareGoldenScenario(page, scenario);
|
||||
await page.waitForTimeout(2600); // the sun wash fades in over 2 s
|
||||
await page.screenshot({ path: `${out}/${scenario.id}.png`, clip: await goldenClip(page, scenario.capture) });
|
||||
console.log('captured', scenario.id);
|
||||
}
|
||||
await browser.close();
|
||||
@@ -12,6 +12,7 @@ const OPENING_COUNT = 100;
|
||||
const PARTITION_COUNT = 60;
|
||||
const COLUMN_COUNT = 40;
|
||||
const DECOR_COUNT = 500;
|
||||
const STAIR_COUNT = 250;
|
||||
|
||||
const round = (value) => Number(value.toFixed(6));
|
||||
|
||||
@@ -119,6 +120,23 @@ const makeDecor = (floor, count) => Array.from({ length: count }, (_, index) =>
|
||||
};
|
||||
});
|
||||
|
||||
const makeStairs = (floor, count) => Array.from({ length: count }, (_, index) => {
|
||||
const column = index % 25;
|
||||
const row = Math.floor(index / 25);
|
||||
const common = {
|
||||
id: `perf-stair-${floor}-${index}`,
|
||||
x: round(0.025 + column * 0.039),
|
||||
y: round(0.03 + row * 0.1),
|
||||
angle: (index % 8) * 45,
|
||||
target_space_id: 'perf-floor-2',
|
||||
};
|
||||
return index % 2 === 0
|
||||
? { ...common, kind: 'straight', direction: index % 4 ? 'forward' : 'backward',
|
||||
length: 0.12, width: 0.045 }
|
||||
: { ...common, kind: 'spiral', direction: index % 4 ? 'clockwise' : 'counterclockwise',
|
||||
radius: 0.05 };
|
||||
});
|
||||
|
||||
const entityKinds = [
|
||||
['light', 'on'],
|
||||
['switch', 'off'],
|
||||
@@ -188,9 +206,10 @@ export const LARGE_HOUSE_COUNTS = Object.freeze({
|
||||
partitions: PARTITION_COUNT,
|
||||
columns: COLUMN_COUNT,
|
||||
decor: DECOR_COUNT,
|
||||
stairs: STAIR_COUNT,
|
||||
});
|
||||
|
||||
export const makeLargeHouseFixture = () => {
|
||||
export const makeLargeHouseFixture = ({ includeStairs = true } = {}) => {
|
||||
let openingsLeft = OPENING_COUNT;
|
||||
let partitionsLeft = PARTITION_COUNT;
|
||||
let columnsLeft = COLUMN_COUNT;
|
||||
@@ -228,6 +247,7 @@ export const makeLargeHouseFixture = () => {
|
||||
partitions: makePartitions(floor, rooms, partitionCount),
|
||||
wall_columns: makeColumns(floor, rooms, columnCount),
|
||||
decor: makeDecor(floor, decorCount),
|
||||
...(includeStairs && floor === 0 ? { stairs: makeStairs(floor, STAIR_COUNT) } : {}),
|
||||
};
|
||||
});
|
||||
const runtime = makeRuntime(spaces);
|
||||
|
||||
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 69 KiB |
@@ -1,40 +1,70 @@
|
||||
{
|
||||
"schema": 3,
|
||||
"matrixVersion": 64,
|
||||
"acceptedAt": "2026-09-25T08:23:19.883Z",
|
||||
"sourceFingerprint": "284838429ce80f96cfa10fb791c6aa3b837dcbbc3c2cf62cdc47d7e6bb6df108",
|
||||
"matrixVersion": 66,
|
||||
"acceptedAt": "2026-09-27T07:07:07.508Z",
|
||||
"sourceFingerprint": "9f31bdc1556b108a5f8910aa9f246ba40507335bdeb00c8b1b2d960f72f84ce7",
|
||||
"chromium": "151.0.7922.34",
|
||||
"capturedOn": "linux",
|
||||
"acceptedOn": "win32",
|
||||
"acceptedOn": "linux",
|
||||
"capture": {
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"chromium": "151.0.7922.34",
|
||||
"buildFingerprint": "284838429ce80f96cfa10fb791c6aa3b837dcbbc3c2cf62cdc47d7e6bb6df108",
|
||||
"ci": {
|
||||
"buildFingerprint": "9f31bdc1556b108a5f8910aa9f246ba40507335bdeb00c8b1b2d960f72f84ce7",
|
||||
"ci": null
|
||||
},
|
||||
"localAttestation": {
|
||||
"schema": "houseplan-golden-wsl/v1",
|
||||
"sha256": "adb377c40236510ed767a5a829dbbc88e51156e8d1f6eb206166fd3a7c61f921",
|
||||
"artifactSha256": "bbcaf4d2621c381224d34fa24d7c14bebb357d2de6342d4ba278a8b9f48556dd",
|
||||
"createdAt": "2026-09-27T07:05:53.848Z",
|
||||
"source": {
|
||||
"repository": "Matysh/houseplan-card",
|
||||
"run": 36111677966,
|
||||
"attempt": 1,
|
||||
"sha": "27d78801d0436f5bd496699a6ee201f8276956a9"
|
||||
"branch": "issue/673-stage6-golden",
|
||||
"commit": "c9a5df602becb3f402cf906c8d9b6b30898439fd",
|
||||
"tree": "d7f59614ce1d307175670c39687e4988b41aaaf8",
|
||||
"remoteSha": "c9a5df602becb3f402cf906c8d9b6b30898439fd",
|
||||
"clean": true,
|
||||
"status": ""
|
||||
},
|
||||
"environment": {
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"kernel": "6.18.33.2-microsoft-standard-WSL2",
|
||||
"wsl": true,
|
||||
"distro": "Ubuntu",
|
||||
"filesystem": "ext2/ext3"
|
||||
},
|
||||
"toolchain": {
|
||||
"node": "22.23.2",
|
||||
"npm": "10.9.8",
|
||||
"playwright": "1.62.0",
|
||||
"chromiumExecutableSha256": "0b20b130e7edd9dd51873be867761295fe0cfad490c2b9a64f95bd3cfc08fa71"
|
||||
}
|
||||
},
|
||||
"localAttestation": null,
|
||||
"foreignCapture": {
|
||||
"reason": "visual acceptance of full Linux CI artifact 36111677966 for v1.78.0-beta.2 on Windows owner workstation"
|
||||
},
|
||||
"foreignCapture": null,
|
||||
"witnesses": {
|
||||
"count": 108,
|
||||
"count": 126,
|
||||
"floor": 10
|
||||
},
|
||||
"scenarios": {
|
||||
"panel-wide-view-light-en": "2c93497871bc40f6401f6cb7e65e73bf98d0064bd2755ca2026135975bd2d9f3",
|
||||
"panel-wide-plan-editor-dark-en": "9404d7e3aa84b4b40a8cfeed27eea8831e55e9e6236abbb7cb092c709ca0dfcd",
|
||||
"isometric-stage6-light-lightfloor": "80ac5d4ed1535fbaa1420b5da1a35599cc8ef9a35e4fa723c89e25ef235f792e",
|
||||
"isometric-stage6-light-darkfloor": "46e25d32a4e4f156586a327cdfbc6f59e79178733b6301c89843b8a8535a738a",
|
||||
"isometric-stage6-dark-darkfloor": "5b76746846c13c2dbca9ade31d4e6cb891a9a486029ebcf4008539a4a92b47fc",
|
||||
"isometric-stage6-dark-lightfloor": "157e0aaf68396b8ed5acc0d2caed935dc0d17c014c265cb2aaeda66944bba8c2",
|
||||
"isometric-stage6-hover-light": "3f8dc5052e883a30e3cf89c1a0fdea0af3bae3c763b6ebaa50e41cba9cdb3bfd",
|
||||
"stairs-flat-normal-light": "df6600545ffbc7611b764eb489e6e3f625ccf51b6ed2f8f83e4df5b7e1565d08",
|
||||
"stairs-flat-hover-dark": "e642373ea6680a978219622105552d4afbf2311eebf101a7c8db280373c9eca3",
|
||||
"stairs-flat-selected-light": "4b37e663d5689c3288ad7dc956b2ec0b42e406d418873c0ea6089c1caf92ec83",
|
||||
"stairs-isometric-dark": "901bc1eee214d179aff13bc7f192a9e270688aa59b2744f3b361e653dc2d0af5",
|
||||
"panel-wide-view-light-en": "275d09b3730361bb491a832fbb76723d90a7327086f042cbf0fdd79f4a002800",
|
||||
"panel-wide-plan-editor-dark-en": "093a3fd1022755f5128261796e009383d7e7a23e9e50f193ab1987d30f9313b2",
|
||||
"panel-narrow-read-only-empty-light-en": "99b8f1af82b1ad13f237125aab8f7e7da0563c138e9da2ddf06e54c2d56138c4",
|
||||
"version-mismatch-desktop-dark-en": "b8059b768284208f605329618799d13491655e488e9d739a40b85cdc0eba5888",
|
||||
"version-mismatch-touch-light-ru": "df42d0158025371b82eabd522e0dd7d0b9121a9ba685970db3d7839d97fce50f",
|
||||
"version-mismatch-desktop-dark-en": "8981a02083caa27a163e9c59319bfdb3f55adf70ee319c9ac9a80739e08dd84e",
|
||||
"version-mismatch-touch-light-ru": "855bb817ffd5628e8bdcc4e5e48367b140ff16e5040e674555fbb52c43d7867c",
|
||||
"version-mismatch-kiosk-attempted-dark-de": "2e3eaf2fb13d1cca77abe97938374b3e25235fc7db87bb6f7a2e72f1cafe4a7e",
|
||||
"hidden-wall-diagnostics-plan-light": "4113474796ccb1b7b8c876fcb463e97eb77bb1ec96129195a0dafdcc7ccca334",
|
||||
"hidden-wall-diagnostics-plan-dark": "40c48868f0251aec3b1a14c0eef77a78cc7965420fb9b41f46f8190d1fdfd11a",
|
||||
"hidden-wall-diagnostics-plan-light": "7bcb8a821b8e0712b1a6c9ad6f36965448db75575ffb02f5d5af0cd328d3c27f",
|
||||
"hidden-wall-diagnostics-plan-dark": "a89b83c64cc7bf0fb4989eceaa4cd24297309221085603f676dc0398da344ecb",
|
||||
"coincident-partition-before-dark": "32d6d7e32db1bb579b33c783833ce986d10a81589e0783c5e7fae4b8c1e2755e",
|
||||
"coincident-partition-thin-dark": "a5bd00dfd6a085b81cf5e213b34669d60edf5c03451ec2943ce31c735497cf8c",
|
||||
"coincident-partition-thick-dark": "848021570e9cdf98bbfa3e9035ac09fe70695ab6b48964f8f947e30ae5695141",
|
||||
@@ -44,44 +74,44 @@
|
||||
"split-corner-wall-thin-dark": "0a8da611ecb6061852cc26ee53c00d12433cf8539681bd91dac3adad1d7e9e67",
|
||||
"split-corner-wall-thick-dark": "2efc65ddaa8777ebffe63205e2a961fa7053c91d3cd8c0af59810bef01ecd498",
|
||||
"split-zero-divider-taper-dark": "f7f9ee435da00dcae043c693127561a5e050675ca4d6f6f0d916ac5dbe24a594",
|
||||
"isometric-geometry-view-dark": "d02f39b803370d0b5b1ffe5c675591b0faf0797f0a34fcd9fee6e04bcc2f87c5",
|
||||
"isometric-geometry-view-light": "aae5bb78d2d49be627d8cbc2ecd960ebeea7d35fe06f36eeef4674c49f136c4e",
|
||||
"isometric-live-layers-dark": "17f41e1d18f7b16d824c437ec85c31e85300a97fc01a8a4200052f394e013d89",
|
||||
"isometric-no-borders-dark": "ec8952f4bbd4011aed80cf66c9fa8345fe4f91fecfe0c4d9f33c5ae3e210805d",
|
||||
"isometric-touch-kiosk-dark": "f478ac634e2367e2753fe2d44b516113425812e5724fd37c2b2ecb89b47c384b",
|
||||
"isometric-large-warm-remount-dark": "354fc12ca28a58866d7ee58794485a87dcdc5887a6c0faa6afe36aaf4a4dd824",
|
||||
"isometric-stage3-overlays-light": "a119a02a7e8755cb398f367a6fefeb6b4dceadd42d60453d11c5d3e83acae829",
|
||||
"isometric-stage3-overlays-dark": "40355196c8578159e13aaa6b0086f6175a473f8f24ceefbde3e67791ffd26135",
|
||||
"isometric-stage3-openings-dark": "702d1c159333a13483405f19bea5b0c580c717ac1c04428fdbaef6d97929c96c",
|
||||
"isometric-stage3-forced-colors-dark": "79e0a916a3bec26c700a8eb280f161e6179685ea05ddf988b04bcb9e9c035747",
|
||||
"isometric-stage3-no-filter-dark": "b0a9336db629283a61666e449cb1d70e537a6a3b85f4faf0a4da24e5877c8fe6",
|
||||
"isometric-geometry-view-dark": "877cde0e1037071a23b3356141c1ddd1a74bcd33da5367ada2809ca6c3ee989b",
|
||||
"isometric-geometry-view-light": "50164a84574bb81b860db94fd07cc5173eb676d4371deb90f3d2e16f5d252398",
|
||||
"isometric-live-layers-dark": "1c3a54350b3bb07b4aa34bf853d22f12622616e1c8208f9d64c5e478c7d92bb2",
|
||||
"isometric-no-borders-dark": "406fa7c7597724cba00df6451a1b81d5c306af0259cc9198e1985b6f325d619d",
|
||||
"isometric-touch-kiosk-dark": "e88e95124300b723e6a3b4563961f7d178a2517f41f7b4a2c9fac3534a57c24f",
|
||||
"isometric-large-warm-remount-dark": "d7932b8baea6b5689480a123698d562bcea2eb0d8536c3516272f5ebef4a4cc8",
|
||||
"isometric-stage3-overlays-light": "3824e3c9901a53a19d2a5e069922e982bc2c8bdf21fc5ba6e8e6477bc2bb1f6d",
|
||||
"isometric-stage3-overlays-dark": "adb042cf55b0c0e75412eea9167aec1df03e24cadb72e8e67d259927d687009b",
|
||||
"isometric-stage3-openings-dark": "33c7c657229b44a3a52bbefe0197302d909b69dc0f1127352410d13f6d0d93f7",
|
||||
"isometric-stage3-forced-colors-dark": "06675a2bf71d405c34dc430e1761f5cb78168d5b52207bb2ca7923f6dd8ce50b",
|
||||
"isometric-stage3-no-filter-dark": "bdec8fce7628934e00a29b2c489226c8b0ca33b3e315fdd34c1f89a804dc125e",
|
||||
"geometry-view-dark-fit": "454da1248f1fe13b8f60c589ae9ebee5e852995e9bc5175d3c5692c445f7cb94",
|
||||
"geometry-view-light-fit": "de5f6a41552447ca909d20471ed6665f558fb6e111c1f333eeba55866517d5a5",
|
||||
"pdf-export-geometry-light": "c403a141cefb73e4653f2ffb9c346b4cfaa3de339b6c39c227ef1552ebde0048",
|
||||
"pdf-export-polish-light": "ed6685900355e20a4618e931d42d2e45a39ad44e4a2ab044fa5ee64ff56f12f4",
|
||||
"pdf-export-stepped-dimensions-light": "b9732b8b9c38153c564d0c93fbfbf44a8dd348c8667d2f8c2d14492e08e04f3e",
|
||||
"vacuum-trail-smoothing-dark": "f8eecc726a3e8bdaba414584ad6afd48908ee9d96ea969a1e37758f4e76843e2",
|
||||
"room-label-parity-view-dark": "a188d4db84533d605cb6fe822d20c34d164e3bfc8458a55beaad564854eef169",
|
||||
"room-label-parity-plan-dark": "07e896d4f6dad384bd52dc60f786f042e1cfcd26ddb1f7b12b72d40bb4824324",
|
||||
"room-label-parity-view-light": "dd96a224c4d951720031ff1ba793a1ba560951e0441cb4f61ee82215aff1d8cc",
|
||||
"room-label-parity-plan-light": "91b7aed42c3a677e387b26c405a04dcc327957f9368625c7c3ad3cca758f1fb1",
|
||||
"room-label-parity-view-dark": "4c00ee3119d3886a5906d5880769558083eb06547a2e2c56e91ae84498416bc9",
|
||||
"room-label-parity-plan-dark": "c0f08f586116c4070723ec89697aa5d966ea57f1c027e7740eeb5147d44d9958",
|
||||
"room-label-parity-view-light": "ec2a1294b0f08019f49a392cdc94970c1545673ebe0d7f22652f602adf00d4e8",
|
||||
"room-label-parity-plan-light": "2603d0c73b39fc72f8b31919fcee15d8b17ea3c6ba1dea74fc791efdd3cfafc6",
|
||||
"washer-active-cycle-dark": "0d683619c27747b3dd6a1e6c7cdc5e15dd886f4f2db4b85b7ca5a5c529e9ec52",
|
||||
"washer-idle-cycle-dark": "0138902daa8dabb0588904507ef5a44f5257f7859b4f1198191bfca3dd4130b7",
|
||||
"day-cycle-dawn-dark": "96bd5ef84345976bb83d35d418e7d247503629eceb732b6e926f2217f3b9faa4",
|
||||
"day-cycle-day-dark": "605849bbada8d3c3a02a2c88c7b21c81c675a7a89b72c4c560df635d63005e67",
|
||||
"day-cycle-dusk-dark": "d19cc97b75014d9f0661705df50d5f30a3a574661957c04c5dab96e26c5e3364",
|
||||
"day-cycle-night-dark": "39a6a959676295eb4ead02703533641f57901ad0ce1359933e1d47f48867196a",
|
||||
"geometry-plan-editor-dark": "6fa29c51ac37449e53ff76afe17760a86325e160f53040c737619aae1095599d",
|
||||
"safe-resize-handles-clamp-light": "4c2856edc3c2c8be740b052d747f144dd6f02e6737ef2df06880eb1a5ec634d4",
|
||||
"safe-resize-handles-clamp-dark": "581f85ca93197ba796e72daa67402eebb7c64f13d4722b5d33943577837c8980",
|
||||
"space-tab-drop-before-light": "2678c840a33ade95f8a038213a696132612cb9bae8290e467ae9c4ac2ee265e3",
|
||||
"space-tab-drop-after-dark": "f4f3ab1bb986426f281753a101b4055fae1f5f1ed0c04c8d41071dfa260978f1",
|
||||
"plan-snap-endpoint-light": "40f761e1a7a12e5113ec6e736c6d7176908d8a1585281c2c2f438bc1fee36e1d",
|
||||
"plan-snap-line-gaps-dark": "e4e5e15a98e35ac26a5b0fe7ef674341fb55d8445587ccc25a81603e1ef92774",
|
||||
"wall-junctions-plan-preview-light": "f6f450768912f37b3d91730cc38f43b399eab781e585bdbe6c9bb29458b56f4c",
|
||||
"wall-junctions-plan-t-dark": "6c3748e931407079e3aad3ade77c46069ef75444c27405549d1610670a33ad95",
|
||||
"geometry-plan-editor-dark": "5e7365ef5820a9c2ea51abd3be0c56900de7932e3da4f78fc8103f307a5fcf65",
|
||||
"safe-resize-handles-clamp-light": "80b3cf9c755dbb77deceaf6deb4569bd1570093509b37e96beb32dd2a07d40da",
|
||||
"safe-resize-handles-clamp-dark": "dd32a1034f349b27717a89516b91a85b916e70080cfeffb127b0b51a24683bff",
|
||||
"space-tab-drop-before-light": "325225df7961482e81931989d1094ab232ca1afac8843febece2d2f124cff04b",
|
||||
"space-tab-drop-after-dark": "c5b2e4089da3f1ddf22f8eb7cd224ddd12ca23680b3800d496f1d218706ed666",
|
||||
"plan-snap-endpoint-light": "3b62f84beade690df3bf957cac80e7e1c66f2d30b2587d3c81419f617f23d31a",
|
||||
"plan-snap-line-gaps-dark": "652277b925c0e4931094e323b14498ff0d44ed19abdde4e26f1e7a28256c5443",
|
||||
"wall-junctions-plan-preview-light": "3d4b493e86ea3b702b326d2dd9db146170faf4ed3810eeb5f02dda8d50b2fd3e",
|
||||
"wall-junctions-plan-t-dark": "395155d3ea4a432b05e81372dd0a039cb47cfe9355ff0fad7a7ab7b9c613bce8",
|
||||
"wall-junctions-view-dark": "b3385dd544e87aeacbc85020621de96ea20feecccf907bc5de3d2c045860a1cd",
|
||||
"junction-patch-resilience-plan-dark": "8ad87deb245097cd090f301d259710323cf4ea577cd8edaf16607d6ad8a3d29d",
|
||||
"junction-patch-resilience-plan-dark": "e35fca8310d84fa0b4b5fb87641d0c3b550270a7fd8b735080d4d08d79801d1d",
|
||||
"junction-patch-resilience-view-dark": "db72b14098dd26132521b71adc87cf5798e5d48faa1f449c294ca2df81077849",
|
||||
"wall-union-isolation-view-light": "2fffbcef2001b24b4ce7d104f0457231044be7ee0718e7f31b6fc13b024f2bc9",
|
||||
"wall-union-isolation-view-dark": "7dce51897ce87e96b7f43d62d68ac859aa662a2aa1227cc853bc86e5bbf321e4",
|
||||
@@ -89,34 +119,34 @@
|
||||
"orthogonal-strip-cell-5-view-dark": "267a3eab1f4ad7d8d484eb2a58b3e38031b0516ca8c46c93e272bfaf9ac409b8",
|
||||
"orthogonal-strip-cell-1-view-dark": "7c9bab0789f9ec9019edf3c49176415af83e8a325e833f98a0e6324e7f06fe4c",
|
||||
"wall-key-roundtrip-view-dark": "74fd46cffefd32d805296d844c162dbe6f15b13e86c4d6c7f6974293b253e1b4",
|
||||
"isometric-wall-junctions-dark": "fe69b10e313298b9cffdd5bc6266e6e9314c4c3b4da85a22af277f31314cca1b",
|
||||
"opening-placement-door-thick-wall-dark": "9d587eff0c2561ef5f84e9280cd812fa6e9af7cf6ff00c922d275796774ba513",
|
||||
"opening-placement-passage-thick-wall-dark": "6ed48b39c0e11fbf4b0df7d3e4dad8078e5b17694f1a780936a864bebd0c1c57",
|
||||
"opening-placement-passage-thick-wall-light": "3d4e3a5d9b67f2450fab9ccc0f5dc91f4485080582377de904f16ea1d62fbafa",
|
||||
"isometric-wall-junctions-dark": "e6bf156d2afb70a631ceade26d6431caf92721a28a661fffd1654f49711fcb3e",
|
||||
"opening-placement-door-thick-wall-dark": "cef2c75a6c60197c24a848fab7e47919437c751aae79ec4d88d744eb70abed43",
|
||||
"opening-placement-passage-thick-wall-dark": "8d089364f6f9aace8a0cccad952731e6d295440b9eb1c8cb75b6e2264b8fa5c5",
|
||||
"opening-placement-passage-thick-wall-light": "14c06cd119a476f8a4edb63db37d55c6d4000f6fbb3ed412cb88e225574896a6",
|
||||
"opening-symbol-room-wall-light": "dc3f6513a08b809a77df6822f59fdb30c912f320e55d47d749963678fbb8f417",
|
||||
"opening-symbol-diagonal-partition-dark": "fe264aa0a5926d904ea0a2d15e5f0567a536c9251a72bf96a287d7bf9c480aa1",
|
||||
"opening-symbol-flip-pairs-light": "693fe97bae8375c6a16244fbb4ddaa1ed447732382a45d4629e1711e0aa0d45a",
|
||||
"isometric-opening-symbol-parity-dark": "cad79850e1477f4a014cd2cf42c4d8d29e96e3cf3190afc6f3336841eff26bfb",
|
||||
"geometry-devices-editor-dark": "e273c4fe49619a5856a9212ff6261803640e5afa79b841edfccf5a4056e0264d",
|
||||
"device-inbox-desktop-en-light": "f90cdc008a7fdadb7fa859776089ce1f0667ef2f764d61e7bd31ae3f4c1033c1",
|
||||
"device-inbox-desktop-ru-dark": "0c45e46ff313540c428ae57e70d0c56e43eeb7a13bf730ecb22ace7bb0b2788e",
|
||||
"device-inbox-narrow-ru-dark": "7b760c9cf4bee09066ee6fde1dc90a93da97d9eaa96a12cee773b8fd596fe5d8",
|
||||
"geometry-decor-editor-dark": "2f8a3742166c10c0a5aa1239c9b6b7d908e7e578dc43229274b17d015c47503c",
|
||||
"tray-wide-selection-en": "5a535b457b8b274ff25085ee992aebd95d756415afe1321d0aadee70610d5801",
|
||||
"tray-wide-tool-ru": "99abefe9c5b0f21356e87b46fc6d6d615cf9b799614c14bf8fffa0261fd9059a",
|
||||
"tray-medium-group-en": "afdb651c687cabda48e62d426b9ef4926a23caa86904fe6fa2c163930070b2bd",
|
||||
"tray-medium-selection-ru": "5d78f5d818514ab80a4fb2949274edffd372e0d2e41bc3ccf39a22fd7c993f6b",
|
||||
"tray-narrow-palette-en": "100d7a87eebf6665bf67cdd53b7f23ade885827af8dc6cc7ae27cebc971b8fb9",
|
||||
"furniture-categories-light": "36676ada243391e7b3b27974df5db1fd33da45508bdcb879b01be239521b830d",
|
||||
"furniture-variants-dark": "7988bb13215657886a1e08ada67317f030c7f99d6d1f483b5d61cebc492cb46b",
|
||||
"furniture-variants-light": "d025401492c808b11fc84e862d3154cd7ccd57095436488575d4b23f7b2debff",
|
||||
"furniture-placement-preview-light": "eeaea99b86807fcf61960ce7c688d929721a3a3ee47867d564079d86d089f7e5",
|
||||
"isometric-opening-symbol-parity-dark": "5dfcd219d9cdc41c382d6e436f15665c0a265ba2b7138b2856e1a50f0e252062",
|
||||
"geometry-devices-editor-dark": "1cc18ac3250a0cc20981b090ef7f964555b237e085e3ddbe10cc7b6081342c8c",
|
||||
"device-inbox-desktop-en-light": "3a1229f1f5134c329ee457a62e555896ffd664d1d6cbd217e4e8248dc0c07dc7",
|
||||
"device-inbox-desktop-ru-dark": "2b0981e7cc134a8f2cfe107c58833e33b862a00a89cadc5033f3b633562f9379",
|
||||
"device-inbox-narrow-ru-dark": "7694119b368baf49fe8c0aeeec46a75bd77c0987266e4a64f5d13a2099e5f098",
|
||||
"geometry-decor-editor-dark": "0fddb5ba477ecad15827215d04d22e9ba1adfacbbfc853712b1e76832182fa5f",
|
||||
"tray-wide-selection-en": "e544ee6b4cde64aae188bac9be9e52c1232a3de8e98e1f68af42d8c7064f0a38",
|
||||
"tray-wide-tool-ru": "d3c49be18142f280c21062612cf06dc8df6e5a076c53aeb2d62da8e3975c8003",
|
||||
"tray-medium-group-en": "6ba416e0ca3cb2fba4a420703318e62d8651053d76ae1936a37537ab486af4a8",
|
||||
"tray-medium-selection-ru": "d5a82d474f75f438c06a340897a9e009e7df0c95c5ef07edd3952f0fe43a0f40",
|
||||
"tray-narrow-palette-en": "d2147c10ec130bead031db16090b0638188a548163ca8dfbfa86e68e71865051",
|
||||
"furniture-categories-light": "8ffc0d18edc76dad428190f7c041ce9fdf9c5cc71a8329116bc1c7ae040a7fd8",
|
||||
"furniture-variants-dark": "368f863d070b8f2f02cf657731aab0e7d80bdf5a817193709bb2838d429ac4e1",
|
||||
"furniture-variants-light": "a702b38b8bd42919a5ed8753b87489545eb6d6538726e5cc3cb856a586834974",
|
||||
"furniture-placement-preview-light": "8a78103d7cdd2808496f6d73b8a65859c4e6b57ee53a0c18f4d90580945673ba",
|
||||
"furniture-plan-art-dark": "e8d3ab381dd33e28c3c05271afeb66b110483e7fcf6e9c660d578c9ae3b4072f",
|
||||
"furniture-new-symbols-light": "51535124c8a68cae621c4fbfc4398edd5bf820caaf6be9a5d2b5fdf962e0b628",
|
||||
"furniture-corrected-art-light": "e7b62ae426c12fa4850429af9d92ca1dd03eedd583d7412112dd30bddf21a7d8",
|
||||
"furniture-transform-light": "ca1db454c8ffe5eb9558bb08f29615b804a4f7b002d90bf51f010b2189d450af",
|
||||
"furniture-transform-light": "ae0772d4a450646f24460a20016f42ea9dda78e7d696c27aee5387d23089f314",
|
||||
"furniture-transform-dark": "04ccddce94dbd35bd3da7e5a40dc5f80adb189e3b5ed6e5e99142258558c207f",
|
||||
"tray-narrow-tool-ru": "fa8f7125ea9f5c43f47ad090cf78ab970950a03dc5a9fa1c348c880ced7c0c4c",
|
||||
"tray-narrow-tool-ru": "62765f588d404e3889f1bbc58e40446535e8b4f322fec3b5dae856c076273511",
|
||||
"geometry-diagonal-45-opening-dark": "a083c9a5b39124f8f6fa8123562f1a5e24c884fa47c54c99786b24164e8bfa78",
|
||||
"openings-thick-wall-dark": "10b672a9965a994928111911760c987b45c7166c60e6bb0cbe02b2c11c59c211",
|
||||
"openings-filled-tunnel-dark": "87edc3afb2720f8b75deb02c2b2817bee9534b7908e4dab8e8fee26cc6daf99d",
|
||||
@@ -170,36 +200,36 @@
|
||||
"large-house-zoom-040-dark": "84f9e93b5fd25caaf031dc7758553c29459d4dda31e086b7d79e904c9b3d6230",
|
||||
"large-house-zoom-250-dark": "fa901d097023320a599c1c80be9f9c65a0ce6245bb7182bc0f3d90d679668c17",
|
||||
"large-house-warm-remount-dark": "6ed51a1c76cd7c62875eadafc169f7c6fc25604e64edabd9594a3664c886987d",
|
||||
"device-dialog-desktop-en": "8e85d6f89bb3665e1588d3382f66c6c063d16a40a19472beec03f571899bf1ac",
|
||||
"device-dialog-desktop-en": "f5d660f9244712f016c02c560a4bd301f3729edd4ca1e63b3d2cf0fab9c71802",
|
||||
"device-dialog-mobile-ru": "2486302565fb8dadb7758384e592b059df45f6f8b92195738e5a0bf62758c713",
|
||||
"device-dialog-desktop-de": "d743bf734ad12f2282e262048eeed97f90c3ca5de2af84bd4d7304794f143351",
|
||||
"room-temperature-dialog-desktop-en": "dd42f01dec3f5c69bae9777c39abed5b8e9e3c076f0046779f9b8a65fec4a90c",
|
||||
"device-dialog-desktop-de": "972d56b0a0307ad7a061175aa0f6147c3170fc64eadb88bafd038835fd2ce331",
|
||||
"room-temperature-dialog-desktop-en": "cea714dbe38185ea3d936fdb7cb71a0205feb74687d3ade517aed28407abd4a9",
|
||||
"room-temperature-dialog-mobile-ru": "5ca1c4f18eab3fb4c0667f76b0d0340df94f24e0457baca89234e0ef159f62d6",
|
||||
"room-discard-dialog-mobile-ru": "9c85af0cdbd2842808ef5ac8fdb306f621a726b0ac77c1449722c85399b4cb3f",
|
||||
"german-view-mobile-light": "8c079d1574a6fbec478eacc872a46800f4348a9ce6d4dc1b1a11f88df8688e19",
|
||||
"toggle-entity-dialog-desktop-en": "d2bc74705c9687d11bd55d22140635c2fb55de6496c7e85dfbb67ac049483f55",
|
||||
"german-view-mobile-light": "1c14ef6e97f0f7c616fe61ad72f4dfa7d94c605efc3d8b26931016c8c13a4282",
|
||||
"toggle-entity-dialog-desktop-en": "075a714a4f013c99614c4941b1202d9ae411e6382f72cb0d0a98e0f669b3388b",
|
||||
"toggle-entity-dialog-mobile-ru": "c5bf5077a33c1acfa5f744e0ab1c15ce6c2147bc3622a7e8754d972aeedb555b",
|
||||
"device-help-popover-light-ru": "0c2f92e0da2546222e8f870cbdadc90775016f9b5f77ae5b5159b790b706b65e",
|
||||
"decor-color-popover-mobile-ru": "de167d5a363758b26108f08f7e3f0ba7a44c8daf7461d1e54d719698d20ab208",
|
||||
"decor-color-popover-desktop-en": "ce6e85df6417dc8912ff7119c9851a7e15c4f28ed4097aa21e973e9da8d1ca0e",
|
||||
"general-color-popover-desktop-en": "9aca14f12cd35f51df24ee2af87d61dff5b6e75926439c054adcc334b1ea60d8",
|
||||
"device-help-popover-light-ru": "7645a419f8214dacc6b97be894643d12b2a0486342e568b0b3ee42d74b8cf878",
|
||||
"decor-color-popover-mobile-ru": "bdf90e0e265d41d032db948a737eeedd6e60cdacc9f85c9356aa809894654960",
|
||||
"decor-color-popover-desktop-en": "46b38745da10d4467b510c5a257102395a797e0d3229e7ce8c89e4bbf3b937e7",
|
||||
"general-color-popover-desktop-en": "b54ef50909c2e7b667d321457b17205d5de38dd39b219eef05bee5ef8fb6bef2",
|
||||
"settings-help-zoom-200-en-light": "9d9cdc0d849afc821efdf4451be40a1d32ecdff06a91b1d517b46c3f45cbcff0",
|
||||
"settings-help-zoom-200-ru-dark": "cb2a9f210b19057e26d42ca8975c3a888fb95de3f6cbd03515d1eccff39de387",
|
||||
"support-desktop-empty-light-en": "ec60104cb98b646db22089c93f70f78c3a13cdb7001782b2981ce19800ce3cee",
|
||||
"support-desktop-preview-dark-en": "37707b251c117eb381f7ddd6ad13a235be82c139a4d92dc07789a76cc425e946",
|
||||
"support-phone-validation-light-ru": "1699dde634aa583ce6aaa30252f6cc01c9d822ff2718d0d77b179c7c38216ea2",
|
||||
"support-phone-success-dark-en": "21146b3a221010cdc45e1a83c299395772a9f214550a296b78c5ecb72ddab332",
|
||||
"support-relay-error-light-en": "e63e4e3aec111aa8044f6c99a8d3331801505a0024399293d8d5d53776346b76",
|
||||
"support-tablet-preview-dark-ru": "969398341e722f54d56a14084d18c4943f644a98b7772222ee7b601d46ddd866",
|
||||
"support-desktop-empty-light-en": "55ffd7a63b8e4d65b8cb695a7b166e0c4bb2e719ce925b8d28b8e8595897750d",
|
||||
"support-desktop-preview-dark-en": "326c1b1ecbe71e0bdaed6445137ee4145418317bf5d5faf10faa297707fdb924",
|
||||
"support-phone-validation-light-ru": "acc540f9459c9dc38981ccb70e75a5415f17c849d3840340648448884bd2ab31",
|
||||
"support-phone-success-dark-en": "b2edd4415043d673d22dfe89ee16b9c190e7f3f9537371f01665e8e41444a00e",
|
||||
"support-relay-error-light-en": "adf30f96a926a1730c9ad34d18b4234d9b19140f11d56c8fb7508976f366c11d",
|
||||
"support-tablet-preview-dark-ru": "a69ba86bf069d9beba94eedf6cc3b385b026caeee66348e4ef42c1a072b6df6a",
|
||||
"device-ripple-color-popover-mobile-ru": "8fc31025d32d87e5d3d3df896937d182577cd5169d520e7cf5fc267e7d080776",
|
||||
"space-room-color-popover-desktop-ru": "031dcd90ca0f66e41876f87b76fc5241d1b1fd99135605b26ab8ce88dfb12111",
|
||||
"backup-full-preview-desktop-en": "935adb062c28385a89d34716616406e2434d8cdf3daf9cb7abead13df3c6cb81",
|
||||
"backup-plan-only-export-desktop-en": "b017b78e75968123011777c27550bf82da6e50267086339b963d8571e8dca679",
|
||||
"backup-space-preview-mobile-ru": "5ee8c2651d9814be9d7c76b7692fcf65f4ce341fb4cde1fc3e854df4568994dc",
|
||||
"optimize-preflight-dialog-dark-en": "be8c9ddaf4eb07d83594d836ea20f5fb0c142d3c0f53a5a5b32e69814df27ae8",
|
||||
"optimize-preflight-dialog-light-ru": "75a42a6dfb8134d7030466a730bd32ed08b7f50a72d2e9e081f9a3d853898856",
|
||||
"optimize-orphan-references-dark-en": "d8a33ed94935d3b09fd9317729dc014c38c9baf4ef425947365d8aab186c45c2",
|
||||
"optimize-orphan-references-light-ru": "d68e22a3cd9b2f047dc017217593a9e1a9df79fb9014070890d4f2df52f38cc2",
|
||||
"space-room-color-popover-desktop-ru": "0e4654978e004cb9242ccf4add8022b57459b6a8524f141160ae49aa46787fe9",
|
||||
"backup-full-preview-desktop-en": "2a71dd52bce193a5ddca1ee0404f91754787206cbe01af0b0bfb3871016ed289",
|
||||
"backup-plan-only-export-desktop-en": "d7ae8031e1147ee8d3d15f35d8cd5bc65aa24fc880ad2feafdd7c8713b76c0c5",
|
||||
"backup-space-preview-mobile-ru": "363aaa313d4b1f6f0b2de66b497390ca876b69cb36dc022b135d33299482465f",
|
||||
"optimize-preflight-dialog-dark-en": "09e2d582f6e9591ac7dc7f11520eed51127c06bf97221a1b5340b04227302a87",
|
||||
"optimize-preflight-dialog-light-ru": "5b679e352bcdbef8437e6098aadd69b27ff8564fd4bb6d04050bd468f60a20f6",
|
||||
"optimize-orphan-references-dark-en": "6a9ab05787bc789af6181e6f2abca19d3cf92ac166aac661ad8deb7c7a3c88bb",
|
||||
"optimize-orphan-references-light-ru": "332e56edb151a5a820fc307d69ffd4060348978572e48704b0a2c14ba9802720",
|
||||
"card-editor-invalid-default-floor-light-en": "dbf251ece014cfee252e0dcb2d7c00eda66aa4b5bd125e4c83e248d4319d24b3",
|
||||
"card-editor-invalid-default-floor-dark-ru": "422b74bb391b87e383350247ef1bc1f10ca48c8f6fe418f75bde94e62e3b8bc0"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 381 KiB After Width: | Height: | Size: 380 KiB |
|
Before Width: | Height: | Size: 364 KiB After Width: | Height: | Size: 363 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 366 KiB After Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 206 KiB After Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 324 KiB After Width: | Height: | Size: 317 KiB |
|
Before Width: | Height: | Size: 312 KiB After Width: | Height: | Size: 304 KiB |
|
Before Width: | Height: | Size: 323 KiB After Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 68 KiB |