mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Вход агента до первого файла кода стоил ≈ 26 700 слов (аудит 22.09). - docs/process/AUTHOR.md и REVIEWER.md — выжимки PROCESS.md: каждый пункт ссылается на раздел канона, ключевые формулировки дословные; test/process-digests.test.mjs сверяет якоря, ссылки и правила. - scripts/entry-cost.mjs — маршрут чтения по роли и бюджет (автор ≤ 12 000 слов, AC1); AGENTS.md «Read this first» называет те же маршруты. - docs/STATUS.md: блок Snapshot генерирует scripts/status-snapshot.mjs (версии — release-contract, счётчики — inventory, теги — git); feature surface и ранние milestones перенесены дословно в docs/STATUS-FEATURES.md. - docs/TESTING.md — действующая инструкция (684 строки, AC3); ручные чек-листы и приложения по issue перенесены дословно в docs/testing-notes/ с индексом и тестом на полноту. - Промпт ревьюера в process.yml читает конспект вместо пересказа правил; машинные требования (строка вердикта, REVIEW_DOC, запрет fetch, таблица «чем краснеет», разделы повторного раунда) сохранены и закреплены тестом. - PROCESS.md: правила не менялись; добавлены ссылка на конспекты в шапке и уточнение в §10.4, что ревьюер конвейера читает конспект. - 7 мутантов в реестре. Issue: #634 User-Visible: no
270 lines
18 KiB
Markdown
270 lines
18 KiB
Markdown
# Диалоги, формы и сводная панель
|
||
|
||
> Приложение к [`docs/TESTING.md`](../TESTING.md): перенесено оттуда дословно (#634).
|
||
> Индекс всех приложений — [`README.md`](README.md).
|
||
|
||
## Вопрос о несохранённых настройках (#610)
|
||
|
||
- [ ] В русской локали четыре формы настроек показывают **Вернуться** и
|
||
**Не сохранять**: первая кнопка сохраняет черновик, вторая закрывает
|
||
форму без сохранения [auto: `demo/smoke_discard_copy.mjs`].
|
||
- [ ] Заголовок и кнопка потери черновика используют
|
||
`mdi:content-save-off-outline`; обычный warning без override сохраняет
|
||
иконки открытого замка [auto: `demo/smoke_discard_copy.mjs`; mutation:
|
||
`discard-confirm-action-icon-falls-back-to-lock`].
|
||
- [ ] RU/EN/DE/FR на 320–640 px, light/dark и DPR 1/2 сохраняют одну строку,
|
||
полный текст, безопасный autofocus и возврат к форме
|
||
[auto: `demo/smoke_dialog_polish_603.mjs`].
|
||
|
||
## Числовые поля со слайдером (#608)
|
||
|
||
- [ ] В настройках комнаты поле размера имени принимает `120` посимвольно:
|
||
до выхода из поля черновик и слайдер остаются на прежнем значении, после
|
||
выхода оба показывают 120; очистка поля возвращает последнее
|
||
подтверждённое значение [auto: `demo/smoke_range_line_draft.mjs`].
|
||
- [ ] В настройках пространства `123` становится 125 только после завершения
|
||
ввода; движение слайдера во время незавершённого ввода отбрасывает текст
|
||
и сразу синхронизирует поле [auto: `demo/smoke_range_line_draft.mjs`,
|
||
`demo/smoke_dialog_config_parity.mjs`].
|
||
- [ ] Возврат раннего clamp на каждом `input` делает свидетель красным
|
||
[mutation: `range-line-clamps-every-keystroke`].
|
||
|
||
## Toggle confirmation state (#103)
|
||
|
||
- [ ] Every executable `ToggleNextEffect` formats current and expected lines
|
||
without deriving direction from the state label; `toggle` names Home
|
||
Assistant as the authority and a no-operation intent produces no lines
|
||
[unit: `device-toggle.test.mjs`].
|
||
- [ ] All-off/mixed/partial groups use only executable targets for their
|
||
active/total count and show skipped targets as a separate line
|
||
[unit: `device-toggle.test.mjs`].
|
||
- [ ] EN/RU confirmation renders prompt → current → expected → skipped before
|
||
the buttons, wraps a long name at 390 px and has no horizontal scroll
|
||
[auto: `smoke_toggle_confirmation.mjs`].
|
||
- [ ] A state race with the same target executes the newly resolved direction;
|
||
a changed target set makes zero service calls and shows the existing
|
||
retry toast [auto: `smoke_toggle_confirmation.mjs`].
|
||
- [ ] Cover, virtual-light, HA-control and Run confirmations keep their
|
||
existing actuation/cancel contracts [auto: `smoke_cover_tap`,
|
||
`smoke_virtual_light_toggle`, `smoke_ha_controls`, `smoke_controls`,
|
||
`smoke_tap_run`].
|
||
|
||
## Unified color and opacity picker (#57)
|
||
|
||
- [ ] RGB↔HSV round trips stay within one RGB channel; 3/6-digit HEX input is
|
||
normalized and invalid drafts never become persisted colors
|
||
[unit: `color-picker.test.mjs`].
|
||
- [ ] Every existing `hp-color-opacity` consumer receives per-card localized
|
||
labels through the unchanged color/opacity event contract, and the shared
|
||
component contains no native `input[type=color]`
|
||
[unit: `color-picker.test.mjs`].
|
||
- [ ] The localized full-width OK button is the final picker control, remains at
|
||
least 40 CSS px high in native and fallback surfaces, and closes without a
|
||
duplicate value event or click-through to the parent card
|
||
[unit: `color-picker.test.mjs`, auto: `smoke_color_picker.mjs`,
|
||
`smoke_help_affordance.mjs`].
|
||
- [ ] Invalid HEX keeps the picker open and focused on its error even after a
|
||
repeated OK; only new valid HEX input unlocks confirmation, while live
|
||
color/opacity updates and the existing outside/trigger/Escape close paths
|
||
retain their values [auto: `smoke_color_picker.mjs`].
|
||
- [ ] At 390 px the one surface exposes hue, saturation, brightness, HEX and
|
||
opacity without horizontal overflow; keyboard Shift+Arrow, touch pointer
|
||
cancellation, invalid HEX recovery, Escape focus return and disabled mode
|
||
remain safe [auto: `smoke_color_picker.mjs`].
|
||
- [ ] The color-only Glow consumer keeps the same picker without an opacity row,
|
||
and native/fallback floating surfaces remain mutually exclusive with help
|
||
[auto: `smoke_help_affordance.mjs`].
|
||
- [ ] The Hue range keeps `0…359`, step 1 and its existing input events while its
|
||
WebKit/Blink and Gecko tracks expose the same cyclic spectrum. A dual
|
||
theme-aware ring keeps the native thumb distinct from every hue, while
|
||
forced-colors falls back to system track/thumb rendering [unit:
|
||
`color-picker.test.mjs`, auto: `smoke_color_picker.mjs`].
|
||
- [ ] The dark mobile and light desktop open-picker goldens are reviewed from the
|
||
complete Linux artifact before a beta; baseline acceptance is not part of
|
||
the implementation loop [golden: `decor-color-popover-mobile-ru`,
|
||
`decor-color-popover-desktop-en`].
|
||
|
||
## Unified picker coverage for every color field (#180)
|
||
|
||
- [ ] A recursive source contract rejects every native `input[type=color]` in
|
||
product TypeScript and fixes the complete shared-component inventory at
|
||
13 template instances [unit: `color-picker.test.mjs`].
|
||
- [ ] The 11 general light/temperature/LQI/Glow/wall palettes and the space room
|
||
colour move their existing opacity into the unified picker; color and
|
||
alpha update one parent draft atomically [unit: `color-picker.test.mjs`,
|
||
auto: `smoke_color_picker_consumers.mjs`].
|
||
- [ ] Global background, space background and activity ripple are color-only;
|
||
opening/closing does not materialize an inherited/default value, and
|
||
Default/Inherit restore `null` without adding alpha
|
||
[auto: `smoke_color_picker_consumers.mjs`].
|
||
- [ ] General settings keep one exclusive picker open among 12 swatches; marker
|
||
activity colour and ripple size use separate, non-overlapping mobile rows,
|
||
ripple size remains independent, and cancelling the space dialog writes
|
||
no color draft [unit: `color-picker.test.mjs`, auto:
|
||
`smoke_color_picker_consumers.mjs`].
|
||
- [ ] The three new dialog families are reviewed from the complete Linux
|
||
artifact before a beta; implementation does not accept their baselines
|
||
[golden: `general-color-popover-desktop-en`,
|
||
`device-ripple-color-popover-mobile-ru`,
|
||
`space-room-color-popover-desktop-ru`].
|
||
|
||
## Contextual help (issues #68 and #86)
|
||
|
||
- [ ] A setting with complete EN/RU/DE/FR help body and ARIA copy shows one 32 px
|
||
desktop / 40 px coarse-pointer button with the outlined circled-question
|
||
icon. Mouse hover, keyboard focus and tap open the same text surface
|
||
[auto: `smoke_help_affordance`].
|
||
- [ ] Empty or whitespace-only help body produces no trigger. A non-empty body
|
||
without a complete ARIA label also produces no trigger; neither case adds
|
||
a tab stop or reserves visible space [auto: `smoke_help_affordance`].
|
||
- [ ] Escape, outside pointer, owning-dialog scroll, toast and a competing colour
|
||
picker close help in the documented order. The Popover and portal fallback
|
||
paths stay inside the visual viewport [auto: `smoke_help_affordance`].
|
||
- [ ] Opening help by hover, focus or tap changes neither `clientHeight`,
|
||
`scrollHeight` nor `scrollTop` of the owning dialog body. The native
|
||
Popover and forced portal fallback have the same no-layout-shift contract
|
||
[auto: `smoke_help_affordance`].
|
||
- [ ] Party 1 contains exactly the 11 agreed logical settings: space scale,
|
||
general and device Glow radius, room fill, source role and controlled
|
||
sources, general/space north, general/space background, zero-thickness
|
||
wall style and the show-hidden catalog filter. The regular and onboarding
|
||
space dialogs expose the same five space-help controls; opening help does
|
||
not change a draft setting or the session-only filter
|
||
[unit: `i18n.test`; auto: `smoke_help_affordance`;
|
||
mutation: `settings-help-party1-placement-removed`].
|
||
- [ ] `marker.controls_hint`, `gs.bg_daynight_hint`, `gs.north_hint` and
|
||
`space.zero_wall_help` are absent from all four dictionaries and production
|
||
templates; state-specific sun/Glow notes remain visible [unit: `i18n.test`].
|
||
|
||
## Help & private feedback (#43)
|
||
|
||
- [ ] Header order is Fit/zoom → General settings → Help; Help has a 44×44
|
||
target, remains available in View and all editors, and is absent in kiosk.
|
||
About appears once in Help, Russian routes to the Russian User Guide and
|
||
every other locale routes to English [unit: `support-feedback.test.mjs`;
|
||
pre-beta: support dialog smoke/golden matrix].
|
||
- [ ] A fresh dialog has empty message/contact and attachment off. Empty or
|
||
over-limit Unicode input cannot submit; Ctrl/Cmd+Enter uses the same
|
||
guard [unit: `support-feedback.test.mjs`; pre-beta: phone validation
|
||
smoke].
|
||
- [ ] Preview builds no external request and exposes exact JSON bytes, size and
|
||
hash. Download equals preview; refresh changes the package namespace;
|
||
expiry/discard/replacement and successful submit invalidate only the
|
||
intended owner/draft token [backend: `test_support_package.py`,
|
||
`test_ha_websocket.py`; pre-beta: browser network capture].
|
||
- [ ] Forbidden sentinels (raw and escaped/base64), unknown fields, names, HA
|
||
ids, URLs/paths and live states never reach package bytes. Geometry and
|
||
referential pseudonyms survive [backend: `test_support_package.py`].
|
||
- [ ] Relay transport uses only the compiled HTTPS host, follows no redirect,
|
||
bounds timeouts/response and never reflects provider text. Relay request,
|
||
rate/idempotency, spool-before-delivery and purge tests run in CI
|
||
[backend: `test_ha_support_transport.py`; relay: `python -m unittest
|
||
discover -s scripts/support-relay/tests -q`].
|
||
- [ ] Success keeps a copyable report id; failure keeps the draft and exact
|
||
attachment with Retry, Copy message, Download and manual links. Old or
|
||
mismatched backend leaves About/Guide usable but exposes no fake submit
|
||
[pre-beta: success/429/timeout/unknown-command smokes in light/dark].
|
||
|
||
## v1.71 audit polish (#434)
|
||
|
||
- [ ] Physical decor inventory counts exact lower-hex allow-listed blob files
|
||
and their real sizes independently of sidecars; a valid-shaped sidecar
|
||
without its blob remains absent from catalog/list/resolve
|
||
[backend: `test_decor_assets.py`; mutations:
|
||
`decor-physical-inventory-follows-sidecars`,
|
||
`decor-catalog-accepts-sidecar-without-blob`].
|
||
- [ ] Exact orphan re-upload repairs metadata at a full physical quota with
|
||
`reused:false`; valid catalog reuse stays `true`, and digest mismatch
|
||
changes no file. Explicit delete removes all and only exact allow-listed
|
||
blob names plus the sidecar [HA: `test_ha_websocket.py`; mutations:
|
||
`decor-orphan-repair-runs-after-quota`,
|
||
`decor-orphan-repair-claims-reuse`, `decor-delete-skips-orphan-blobs`].
|
||
- [ ] Static cards learn exact `decor_assets_api:1` only from fresh config/get,
|
||
revoke it on downgrade and never resolve without it. Positive and missing
|
||
resolve caches share only connection + config revision + sorted id set;
|
||
failed calls retry [unit: `config-store`, `space-card-audit-lows`,
|
||
`decor-assets`; smoke: `smoke_space_card_decor_capability`].
|
||
- [ ] Ready→warm cancels an existing dangerous-action promise and removes its
|
||
dialog while preserving the committed body; a request in warm refuses,
|
||
and warm→ready allows immediately before another render
|
||
[smoke: `smoke_danger_confirm_branches`; mutations:
|
||
`danger-confirm-warm-language-guard-removed`,
|
||
`danger-confirm-warm-transition-cancel-removed`].
|
||
- [ ] Area absence evidence cannot outlive its current snapshot binding
|
||
[unit: `device-area-relocation`; mutation:
|
||
`area-cleanup-keeps-candidate-outside-current-snapshot`].
|
||
- [ ] The smoke job has its own 20-minute bound and each file is wrapped by GNU
|
||
`timeout --kill-after=10s 180s`; both German route waits have one-second
|
||
diagnostics [unit: `smoke-exception-guard`; CI: `validate.yml`].
|
||
- [ ] Every well-formed token from an invalid, stale or locally unadoptable
|
||
support preview response is discarded exactly once; malformed tokens are
|
||
not echoed and cleanup never hides the original failure
|
||
[smoke: `smoke_support_feedback`; mutation:
|
||
`support-invalid-response-leaks-issued-token`].
|
||
|
||
## Надёжность сводной панели (#493)
|
||
|
||
- [ ] `test/summary-panel.test.mjs` строит индекс по 10 000 HA entities,
|
||
находит exact `entity_id` через полный индекс, ограничивает выдачу 100
|
||
строками и доказывает 0 rebuild при смене state value и ровно один при
|
||
add/remove/friendly-name change.
|
||
- [ ] `test/summary-panel-runtime.test.mjs` доказывает authority локальных
|
||
200%/150% при repeated same-key `setConfig`, stable-id ownership после
|
||
reorder/delete и lifecycle generation при route/user/permission/kiosk,
|
||
disconnect/reconnect и поздних async completion.
|
||
- [ ] `tests_backend/test_summary_panel.py`, `test_ha_websocket.py` и
|
||
`test_ha_import_export.py` проверяют одну writer matrix: omission/explicit
|
||
empty, readable и старую broken reference, Optimize, full/space/plan-only
|
||
import, Optimize/Import Undo, copy и delete вместе с independent-setting
|
||
sentinels.
|
||
- [ ] Перед `S7-code-review`: `node demo/smoke_summary_panel.mjs`. Fixture
|
||
содержит 200 rows и 10 000 states; закрытые строки не содержат entity
|
||
options, открыт ровно один picker (до 100 entity + 3 system + current
|
||
broken), а после трёх warmups и 20 samples действуют бюджеты open p95
|
||
≤250 ms и input p95 ≤50 ms. Тот же smoke проверяет 320/390 CSS px,
|
||
RU/EN, light/dark, admin/household/kiosk и 200% text без горизонтального
|
||
overflow и с targets не меньше 44 px.
|
||
- [ ] `node scripts/mutation-gate.mjs --changed origin/dev..HEAD` обязан поймать
|
||
снятие DOM bound, state-value rebuild, local-scale authority, lifecycle
|
||
generation и Optimize writer guard. Полные golden/smoke/performance и
|
||
Linux CI HA harness остаются обязательным гейтом точного SHA беты.
|
||
|
||
## Идентичность сводной панели в Masonry (#561)
|
||
|
||
- [ ] `test/summary-panel.test.mjs` пересоздаёт весь production-shaped
|
||
`hui-masonry-view`: canonical `cards=[A,B,C]`, wide-колонки `[A,C] [B]`
|
||
и новый narrow DOM `[A,B,C]`. Slots обязаны остаться
|
||
`masonry-v2:0/1/2`, без коллизии old C → new B.
|
||
- [ ] Тот же unit проверяет live reflow, nested stack через ShadowRoot,
|
||
remount внутренней House Plan card, два nested instances и повторную
|
||
попытку resolution после временно отсутствующего `masonry.cards`.
|
||
- [ ] `test/summary-panel-runtime.test.mjs` доказывает отсутствие localStorage
|
||
read/write при unresolved identity, отсутствие импорта/удаления старого
|
||
DOM-path key и независимое восстановление show/scale одинаковых карточек
|
||
после полного reload.
|
||
- [ ] Мутант `summary-masonry-identity-uses-visual-dom-path` возвращает
|
||
структурный DOM path вместо canonical index; focused #561 unit обязан
|
||
покраснеть. Перед бетой selected summary-panel smoke проверяет ротацию
|
||
touch/kiosk viewport по общему release-процессу.
|
||
|
||
## Доступность основного View (#565)
|
||
|
||
- [ ] `test/device-presentation.test.mjs` доказывает whole-segment
|
||
дедупликацию доступного имени во всех четырёх локалях, сохранение порядка
|
||
и отсутствие ложного удаления при частичном совпадении.
|
||
- [ ] `demo/smoke_household_journeys.mjs` проверяет именованную навигацию и
|
||
единственный `aria-current="page"`, реальный Tab-focus tooltip, переход к
|
||
следующему устройству, blur-cleanup и попадание подсказки в viewport.
|
||
Комбинированный mouse+keyboard сценарий доказывает, что комната не
|
||
заменяет focus-tooltip, другое устройство сохраняет обычный hover, а
|
||
pointerleave восстанавливает подсказку всё ещё сфокусированного устройства.
|
||
- [ ] `demo/smoke_space_card.mjs` считает сегменты тревоги в доступном имени
|
||
статической карточки и не добавляет ей интерактивность.
|
||
- [ ] Мутанты `view-current-space-aria-removed`,
|
||
`device-accessible-label-dedup-removed`,
|
||
`device-focus-tooltip-handler-removed` и
|
||
`device-focus-tooltip-blur-cleanup-removed`, а также мутанты раунда r2
|
||
`device-focus-tooltip-room-hover-overwrites` и
|
||
`device-pointer-leave-clears-focus-fallback` обязаны покраснеть на своих
|
||
заявленных гардах.
|