refactor: comprehensive code quality, security, and release readiness improvements
Lint & Test / test (push) Failing after 48s

Security: tighten CORS defaults, add webhook rate limiting, fix XSS in
automations, guard WebSocket JSON.parse, validate ADB address input,
seal debug exception leak, URL-encode WS tokens, CSS.escape in selectors.

Code quality: add Pydantic models for brightness/power endpoints, fix
thread safety and name uniqueness in DeviceStore, immutable update
pattern, split 6 oversized files into 16 focused modules, enable
TypeScript strictNullChecks (741→102 errors), type state variables,
add dom-utils helper, migrate 3 modules from inline onclick to event
delegation, ProcessorDependencies dataclass.

Performance: async store saves, health endpoint log level, command
palette debounce, optimized entity-events comparison, fix service
worker precache list.

Testing: expand from 45 to 293 passing tests — add store tests (141),
route tests (25), core logic tests (42), E2E flow tests (33), organize
into tests/api/, tests/storage/, tests/core/, tests/e2e/.

DevOps: CI test pipeline, pre-commit config, Dockerfile multi-stage
build with non-root user and health check, docker-compose improvements,
version bump to 0.2.0.

Docs: rewrite CLAUDE.md (202→56 lines), server/CLAUDE.md (212→76),
create contexts/server-operations.md, fix .js→.ts references, fix env
var prefix in README, rewrite INSTALLATION.md, add CONTRIBUTING.md and
.env.example.
This commit is contained in:
2026-03-22 00:38:28 +03:00
parent 07bb89e9b7
commit f2871319cb
115 changed files with 9808 additions and 5818 deletions
@@ -15,10 +15,11 @@ import { scenePresetsCache, outputTargetsCache, automationsCacheObj } from '../c
import { TagInput, renderTagChips } from '../core/tag-input.ts';
import { cardColorStyle, cardColorButton } from '../core/card-colors.ts';
import { EntityPalette } from '../core/entity-palette.ts';
import { navigateToCard } from '../core/navigation.ts';
import type { ScenePreset } from '../types.ts';
let _editingId: string | null = null;
let _allTargets = []; // fetched on capture open
let _allTargets: any[] = []; // fetched on capture open
let _sceneTagsInput: TagInput | null = null;
class ScenePresetEditorModal extends Modal {
@@ -76,7 +77,7 @@ export function createSceneCard(preset: ScenePreset) {
const colorStyle = cardColorStyle(preset.id);
return `<div class="card" data-scene-id="${preset.id}"${colorStyle ? ` style="${colorStyle}"` : ''}>
<div class="card-top-actions">
<button class="card-remove-btn" onclick="deleteScenePreset('${preset.id}', '${escapeHtml(preset.name)}')" title="${t('scenes.delete')}">&#x2715;</button>
<button class="card-remove-btn" data-action="delete-scene" data-id="${preset.id}" title="${t('scenes.delete')}">&#x2715;</button>
</div>
<div class="card-header">
<div class="card-title" title="${escapeHtml(preset.name)}">${escapeHtml(preset.name)}</div>
@@ -88,10 +89,10 @@ export function createSceneCard(preset: ScenePreset) {
</div>
${renderTagChips(preset.tags)}
<div class="card-actions">
<button class="btn btn-icon btn-secondary" onclick="cloneScenePreset('${preset.id}')" title="${t('common.clone')}">${ICON_CLONE}</button>
<button class="btn btn-icon btn-secondary" onclick="editScenePreset('${preset.id}')" title="${t('scenes.edit')}">${ICON_EDIT}</button>
<button class="btn btn-icon btn-secondary" onclick="recaptureScenePreset('${preset.id}')" title="${t('scenes.recapture')}">${ICON_REFRESH}</button>
<button class="btn btn-icon btn-success" onclick="activateScenePreset('${preset.id}')" title="${t('scenes.activate')}">${ICON_START}</button>
<button class="btn btn-icon btn-secondary" data-action="clone-scene" data-id="${preset.id}" title="${t('common.clone')}">${ICON_CLONE}</button>
<button class="btn btn-icon btn-secondary" data-action="edit-scene" data-id="${preset.id}" title="${t('scenes.edit')}">${ICON_EDIT}</button>
<button class="btn btn-icon btn-secondary" data-action="recapture-scene" data-id="${preset.id}" title="${t('scenes.recapture')}">${ICON_REFRESH}</button>
<button class="btn btn-icon btn-success" data-action="activate-scene" data-id="${preset.id}" title="${t('scenes.activate')}">${ICON_START}</button>
${cardColorButton(preset.id, 'data-scene-id')}
</div>
</div>`;
@@ -106,7 +107,7 @@ export async function loadScenePresets(): Promise<ScenePreset[]> {
export function renderScenePresetsSection(presets: ScenePreset[]): string | { headerExtra: string; content: string } {
if (!presets || presets.length === 0) return '';
const captureBtn = `<button class="btn btn-sm btn-primary dashboard-stop-all" onclick="event.stopPropagation(); openScenePresetCapture()" title="${t('scenes.capture')}">${ICON_CAPTURE} ${t('scenes.capture')}</button>`;
const captureBtn = `<button class="btn btn-sm btn-primary dashboard-stop-all" data-action="capture-scene" title="${t('scenes.capture')}">${ICON_CAPTURE} ${t('scenes.capture')}</button>`;
const cards = presets.map(p => _renderDashboardPresetCard(p)).join('');
return { headerExtra: captureBtn, content: `<div class="dashboard-autostart-grid">${cards}</div>` };
@@ -120,7 +121,7 @@ function _renderDashboardPresetCard(preset: ScenePreset): string {
].filter(Boolean).join(' \u00b7 ');
const pStyle = cardColorStyle(preset.id);
return `<div class="dashboard-target dashboard-scene-preset dashboard-card-link" data-scene-id="${preset.id}" onclick="if(!event.target.closest('button')){navigateToCard('automations',null,'scenes','data-scene-id','${preset.id}')}"${pStyle ? ` style="${pStyle}"` : ''}>
return `<div class="dashboard-target dashboard-scene-preset dashboard-card-link" data-scene-id="${preset.id}" data-action="navigate-scene" data-id="${preset.id}"${pStyle ? ` style="${pStyle}"` : ''}>
<div class="dashboard-target-info">
<span class="dashboard-target-icon">${ICON_SCENE}</span>
<div>
@@ -130,7 +131,7 @@ function _renderDashboardPresetCard(preset: ScenePreset): string {
</div>
</div>
<div class="dashboard-target-actions">
<button class="dashboard-action-btn start" onclick="activateScenePreset('${preset.id}')" title="${t('scenes.activate')}">${ICON_START}</button>
<button class="dashboard-action-btn start" data-action="activate-scene" data-id="${preset.id}" title="${t('scenes.activate')}">${ICON_START}</button>
</div>
</div>`;
}
@@ -155,7 +156,7 @@ export async function openScenePresetCapture(): Promise<void> {
selectorGroup.style.display = '';
targetList.innerHTML = '';
try {
_allTargets = await outputTargetsCache.fetch().catch(() => []);
_allTargets = await outputTargetsCache.fetch().catch((): any[] => []);
_refreshTargetSelect();
} catch { /* ignore */ }
}
@@ -190,7 +191,7 @@ export async function editScenePreset(presetId: string): Promise<void> {
selectorGroup.style.display = '';
targetList.innerHTML = '';
try {
_allTargets = await outputTargetsCache.fetch().catch(() => []);
_allTargets = await outputTargetsCache.fetch().catch((): any[] => []);
// Pre-add targets already in the preset
const presetTargetIds = (preset.targets || []).map(pt => pt.target_id || pt.id);
@@ -200,7 +201,7 @@ export async function editScenePreset(presetId: string): Promise<void> {
const item = document.createElement('div');
item.className = 'scene-target-item';
item.dataset.targetId = tid;
item.innerHTML = `<span>${escapeHtml(tgt.name)}</span><button type="button" class="btn-remove-condition" onclick="removeSceneTarget(this)" title="Remove">&#x2715;</button>`;
item.innerHTML = `<span>${escapeHtml(tgt.name)}</span><button type="button" class="btn-remove-condition" data-action="remove-scene-target" title="Remove">&#x2715;</button>`;
targetList.appendChild(item);
}
_refreshTargetSelect();
@@ -294,7 +295,7 @@ function _addTargetToList(targetId: string, targetName: string): void {
const item = document.createElement('div');
item.className = 'scene-target-item';
item.dataset.targetId = targetId;
item.innerHTML = `<span>${ICON_TARGET} ${escapeHtml(targetName)}</span><button type="button" class="btn-remove-condition" onclick="removeSceneTarget(this)" title="Remove">&#x2715;</button>`;
item.innerHTML = `<span>${ICON_TARGET} ${escapeHtml(targetName)}</span><button type="button" class="btn-remove-condition" data-action="remove-scene-target" title="Remove">&#x2715;</button>`;
list.appendChild(item);
_refreshTargetSelect();
}
@@ -320,10 +321,7 @@ export async function addSceneTarget(): Promise<void> {
if (tgt) _addTargetToList(tgt.id, tgt.name);
}
export function removeSceneTarget(btn: HTMLElement): void {
btn.closest('.scene-target-item').remove();
_refreshTargetSelect();
}
// removeSceneTarget is now handled via event delegation on the modal
// ===== Activate =====
@@ -403,7 +401,7 @@ export async function cloneScenePreset(presetId: string): Promise<void> {
selectorGroup.style.display = '';
targetList.innerHTML = '';
try {
_allTargets = await outputTargetsCache.fetch().catch(() => []);
_allTargets = await outputTargetsCache.fetch().catch((): any[] => []);
// Pre-add targets from the cloned preset
const clonedTargetIds = (preset.targets || []).map(pt => pt.target_id || pt.id);
@@ -413,7 +411,7 @@ export async function cloneScenePreset(presetId: string): Promise<void> {
const item = document.createElement('div');
item.className = 'scene-target-item';
item.dataset.targetId = tid;
item.innerHTML = `<span>${escapeHtml(tgt.name)}</span><button type="button" class="btn-remove-condition" onclick="removeSceneTarget(this)" title="Remove">&#x2715;</button>`;
item.innerHTML = `<span>${escapeHtml(tgt.name)}</span><button type="button" class="btn-remove-condition" data-action="remove-scene-target" title="Remove">&#x2715;</button>`;
targetList.appendChild(item);
}
_refreshTargetSelect();
@@ -456,6 +454,57 @@ export async function deleteScenePreset(presetId: string): Promise<void> {
}
}
// ===== Event delegation for scene preset card actions =====
const _sceneCardActions: Record<string, (id: string) => void> = {
'delete-scene': deleteScenePreset,
'clone-scene': cloneScenePreset,
'edit-scene': editScenePreset,
'recapture-scene': recaptureScenePreset,
'activate-scene': activateScenePreset,
};
export function initScenePresetDelegation(container: HTMLElement): void {
container.addEventListener('click', (e: MouseEvent) => {
const btn = (e.target as HTMLElement).closest<HTMLElement>('[data-action]');
if (!btn) return;
const action = btn.dataset.action;
const id = btn.dataset.id;
if (!action) return;
if (action === 'capture-scene') {
e.stopPropagation();
openScenePresetCapture();
return;
}
if (action === 'navigate-scene') {
// Only navigate if click wasn't on a child button
if ((e.target as HTMLElement).closest('button')) return;
navigateToCard('automations', null, 'scenes', 'data-scene-id', id);
return;
}
if (action === 'remove-scene-target') {
const item = btn.closest('.scene-target-item');
if (item) {
item.remove();
_refreshTargetSelect();
}
return;
}
if (!id) return;
const handler = _sceneCardActions[action];
if (handler) {
e.stopPropagation();
handler(id);
}
});
}
// ===== Helpers =====
function _reloadScenesTab(): void {
@@ -466,3 +515,18 @@ function _reloadScenesTab(): void {
// Also refresh dashboard (scene presets section)
if (typeof window.loadDashboard === 'function') window.loadDashboard(true);
}
// ===== Modal event delegation (for target list remove buttons) =====
const _sceneEditorModal = document.getElementById('scene-preset-editor-modal');
if (_sceneEditorModal) {
_sceneEditorModal.addEventListener('click', (e: MouseEvent) => {
const btn = (e.target as HTMLElement).closest<HTMLElement>('[data-action="remove-scene-target"]');
if (!btn) return;
const item = btn.closest('.scene-target-item');
if (item) {
item.remove();
_refreshTargetSelect();
}
});
}