fix(editors): свидетель карточек отдельным смоком, два списка в сегменты

Закрывает находки код-ревью r1 по #598.

M1 — неприкосновенный смок восстановлен. `demo/smoke_tap_run.mjs` вернулся
побайтово к версии `origin/dev`: он один из девяти `.srcrow`-смоков, которые
ТЗ объявило неизменяемыми, и правка в нём обесценивала собственное
доказательство AC2/AC3. Проверки карточек переехали в новый
`demo/smoke_settings_dialog_cards.mjs` — отдельного свидетеля ТЗ не запрещало.

M2 — сегментированный переключатель сделан там, где он обещан и уместен:
`space-bg-mode` и `space-zero-wall-style` переведены из `<select>` в
`segmented`. Остальные выборы в трёх диалогах остаются списками осознанно:
привязка маркера, роль света и режим свечения уже `role="radiogroup"`
(`.markerradios`), позиция значения закреплена смоком
`smoke_device_preview_parity`, списки сущностей и тем длинные. Сокращение
объявлено в ТЗ и в обоих changelog.

M3 — мутант `state-callout-hidden-under-help` патчит теперь оба состояния:
`marker.run_target_gone` и `gs.sun_missing`; гард — новый смок.

Заодно: `ensureFormKitStyles(this.host)` не вызывался ни в одном из трёх
диалогов — стили набора приезжали только из диалога комнаты, и без него
сегменты имели высоту 14 px вместо 44. Нашёл новый смок проверкой
`bgSegmentTargetIsBigEnough`.

Гейты: typecheck, test (2788 pass), build, bundle:sync, три смока,
mutation-gate --check, оба мутанта задачи.

Issue: #598
User-Visible: yes
This commit is contained in:
Claude
2026-09-19 19:17:20 +03:00
parent ad294b9992
commit bd6da53f04
50 changed files with 657 additions and 604 deletions
+2 -1
View File
@@ -11,7 +11,7 @@
*/
import { html, nothing, type TemplateResult } from 'lit';
import { formCard, segmented } from './form-kit';
import { ensureFormKitStyles, formCard, segmented } from './form-kit';
import { langOf } from '../i18n';
import { supportT } from '../i18n/support';
import { DEFAULT_FILL_COLORS } from '../logic';
@@ -23,6 +23,7 @@ import type { ZigbeeTopologySettings } from '../zigbee-topology-settings';
import type { HouseplanEditorRuntime } from '../houseplan-editor-runtime';
export function renderGeneralSettingsDialog(this: HouseplanEditorRuntime): TemplateResult {
ensureFormKitStyles(this.host);
return html`<hp-dialog .hass=${this.host.hass} data-kind="settings" .title=${this.host._t('gs.title')} icon="mdi:cog-outline" wide
@hp-close=${() => (this.host._settingsDialog = null)}>
<div class="body hpf-form">
+2 -1
View File
@@ -7,7 +7,7 @@
*/
import { html, nothing, type TemplateResult } from 'lit';
import { formCard } from './form-kit';
import { ensureFormKitStyles, formCard } from './form-kit';
import { resolveDevicePresentation } from '../device-presentation';
import { toggleEntityCandidates } from '../device-toggle';
@@ -35,6 +35,7 @@ const DISPLAY_HINT_KEYS: Record<DeviceDisplayMode, I18nKey> = {
export function renderMarkerDialog(this: HouseplanEditorRuntime): TemplateResult {
ensureFormKitStyles(this.host);
const d = this.host._markerDialog!;
const isVirtual = d.bindingMode === 'virtual';
const cands = this._bindingCandidates();
+25 -20
View File
@@ -7,7 +7,7 @@
*/
import { html, nothing, type TemplateResult } from 'lit';
import { formCard, segmented } from './form-kit';
import { ensureFormKitStyles, formCard, segmented } from './form-kit';
import { gridCellFieldToCm, gridCellFieldValue } from '../grid-scale';
import { DEFAULT_CUSTOM_FILL, SPACE_FILL_UI_MODES, stageBgOf } from '../logic';
@@ -23,6 +23,7 @@ export const CELL_CM_MAX = 1000;
export function renderSpaceSettingsDialog(this: HouseplanEditorRuntime): TemplateResult {
ensureFormKitStyles(this.host);
const d = this.host._spaceDialog!;
if (d.copy) return renderSpaceCopyDialog(this.host, () => { void this._saveSpaceCopy(); });
const progress = this.host._importTotal > 0 && d.mode === 'create'
@@ -118,20 +119,20 @@ export function renderSpaceSettingsDialog(this: HouseplanEditorRuntime): Templat
<label for="space-zero-wall-style">${this.host._t('space.zero_wall_style')}</label>
${this._help('space.zero_wall_style.help')}
</div>
<select id="space-zero-wall-style" class="areasel"
@change=${(e: Event) => {
const value = (e.target as HTMLSelectElement).value;
${segmented({
name: 'space-zero-wall-style',
value: d.zeroWallStyle === 'solid' ? 'solid' : 'dashed',
ariaLabel: this.host._t('space.zero_wall_style'),
options: [
{ value: 'dashed', label: this.host._t('space.zero_wall_dashed') },
{ value: 'solid', label: this.host._t('space.zero_wall_solid') },
],
onChange: (value) => {
this.host._spaceDialog = {
...d, zeroWallStyle: value === 'solid' ? 'solid' : 'dashed',
};
}}>
<option value="dashed" ?selected=${d.zeroWallStyle === 'dashed'}>
${this.host._t('space.zero_wall_dashed')}
</option>
<option value="solid" ?selected=${d.zeroWallStyle === 'solid'}>
${this.host._t('space.zero_wall_solid')}
</option>
</select>
},
})}
<label class="srcrow">
${this._boolInput(d.showNames, (v) => (this.host._spaceDialog = touchSpaceDisplay(d, 'showNames', v)))}
<span>${this.host._t('space.show_names')}</span>
@@ -238,15 +239,19 @@ export function renderSpaceSettingsDialog(this: HouseplanEditorRuntime): Templat
<label for="space-bg-mode">${this.host._t('space.bg_mode')}</label>
${this._help('space.bg_mode.help')}
</div>
<select id="space-bg-mode" class="areasel"
@change=${(e: Event) => {
const v = (e.target as HTMLSelectElement).value;
${segmented({
name: 'space-bg-mode',
value: d.bgMode ?? '',
ariaLabel: this.host._t('space.bg_mode'),
options: [
{ value: '', label: this.host._t('space.sun_inherit') },
{ value: 'static', label: this.host._t('gs.bg_static') },
{ value: 'daynight', label: this.host._t('gs.bg_daynight') },
],
onChange: (v) => {
this.host._spaceDialog = { ...d, bgMode: v === 'static' || v === 'daynight' ? (v as any) : null };
}}>
<option value="" ?selected=${d.bgMode === null}>${this.host._t('space.sun_inherit')}</option>
<option value="static" ?selected=${d.bgMode === 'static'}>${this.host._t('gs.bg_static')}</option>
<option value="daynight" ?selected=${d.bgMode === 'daynight'}>${this.host._t('gs.bg_daynight')}</option>
</select>
},
})}
${(d.bgMode ?? bgModeOf(this.host._settings, {})) === 'static'
? html`<div class="colorrow">
<hp-color-opacity .label=${this.host._t('space.bg_color')}