refactor: comprehensive code quality, security, and release readiness improvements
Some checks failed
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

View File

@@ -87,7 +87,7 @@ export function createPatternTemplateCard(pt: PatternTemplate) {
export async function showPatternTemplateEditor(templateId: string | null = null, cloneData: PatternTemplate | null = null): Promise<void> {
try {
// Load sources for background capture
const sources = await streamsCache.fetch().catch(() => []);
const sources = await streamsCache.fetch().catch((): any[] => []);
const bgSelect = document.getElementById('pattern-bg-source') as HTMLSelectElement;
bgSelect.innerHTML = '';
@@ -116,7 +116,7 @@ export async function showPatternTemplateEditor(templateId: string | null = null
setPatternEditorSelectedIdx(-1);
setPatternCanvasDragMode(null);
let _editorTags = [];
let _editorTags: string[] = [];
if (templateId) {
const resp = await fetch(`${API_BASE}/pattern-templates/${templateId}`, { headers: getHeaders() });
@@ -340,7 +340,7 @@ export function removePatternRect(index: number): void {
export function renderPatternCanvas(): void {
const canvas = document.getElementById('pattern-canvas') as HTMLCanvasElement;
if (!canvas) return;
const ctx = canvas.getContext('2d');
const ctx = canvas.getContext('2d')!;
const w = canvas.width;
const h = canvas.height;
@@ -396,8 +396,8 @@ export function renderPatternCanvas(): void {
ctx.strokeRect(rx, ry, rw, rh);
// Edge highlight
let edgeDir = null;
if (isDragging && patternCanvasDragMode.startsWith('resize-')) {
let edgeDir: string | null = null;
if (isDragging && patternCanvasDragMode?.startsWith('resize-')) {
edgeDir = patternCanvasDragMode.replace('resize-', '');
} else if (isHovered && patternEditorHoverHit && patternEditorHoverHit !== 'move') {
edgeDir = patternEditorHoverHit;
@@ -586,16 +586,16 @@ function _patternCanvasDragMove(e: MouseEvent | { clientX: number; clientY: numb
const mx = (e.clientX - canvasRect.left) * scaleX;
const my = (e.clientY - canvasRect.top) * scaleY;
const dx = (mx - patternCanvasDragStart.mx) / w;
const dy = (my - patternCanvasDragStart.my) / h;
const orig = patternCanvasDragOrigRect;
const dx = (mx - patternCanvasDragStart!.mx!) / w;
const dy = (my - patternCanvasDragStart!.my!) / h;
const orig = patternCanvasDragOrigRect!;
const r = patternEditorRects[patternEditorSelectedIdx];
if (patternCanvasDragMode === 'move') {
r.x = Math.max(0, Math.min(1 - r.width, orig.x + dx));
r.y = Math.max(0, Math.min(1 - r.height, orig.y + dy));
} else if (patternCanvasDragMode.startsWith('resize-')) {
const dir = patternCanvasDragMode.replace('resize-', '');
} else if (patternCanvasDragMode?.startsWith('resize-')) {
const dir = patternCanvasDragMode!.replace('resize-', '');
let nx = orig.x, ny = orig.y, nw = orig.width, nh = orig.height;
if (dir.includes('w')) { nx = orig.x + dx; nw = orig.width - dx; }
if (dir.includes('e')) { nw = orig.width + dx; }
@@ -631,7 +631,7 @@ function _patternCanvasDragEnd(e: MouseEvent): void {
const my = (e.clientY - canvasRect.top) * scaleY;
let cursor = 'default';
let newHoverIdx = -1;
let newHoverHit = null;
let newHoverHit: string | null = null;
if (e.clientX >= canvasRect.left && e.clientX <= canvasRect.right &&
e.clientY >= canvasRect.top && e.clientY <= canvasRect.bottom) {
for (let i = patternEditorRects.length - 1; i >= 0; i--) {
@@ -717,8 +717,8 @@ function _patternCanvasMouseDown(e: MouseEvent | { offsetX?: number; offsetY?: n
const rect = canvas.getBoundingClientRect();
const scaleX = w / rect.width;
const scaleY = h / rect.height;
const mx = (e.offsetX !== undefined ? e.offsetX : e.clientX - rect.left) * scaleX;
const my = (e.offsetY !== undefined ? e.offsetY : e.clientY - rect.top) * scaleY;
const mx = (e.offsetX !== undefined ? e.offsetX : (e.clientX ?? 0) - rect.left) * scaleX;
const my = (e.offsetY !== undefined ? e.offsetY : (e.clientY ?? 0) - rect.top) * scaleY;
// Check delete button on hovered or selected rects first
for (const idx of [patternEditorHoveredIdx, patternEditorSelectedIdx]) {
@@ -739,7 +739,7 @@ function _patternCanvasMouseDown(e: MouseEvent | { offsetX?: number; offsetY?: n
// Test all rects; selected rect takes priority so it stays interactive
// even when overlapping with others.
const selIdx = patternEditorSelectedIdx;
const testOrder = [];
const testOrder: number[] = [];
if (selIdx >= 0 && selIdx < patternEditorRects.length) testOrder.push(selIdx);
for (let i = patternEditorRects.length - 1; i >= 0; i--) {
if (i !== selIdx) testOrder.push(i);
@@ -795,15 +795,15 @@ function _patternCanvasMouseMove(e: MouseEvent | { offsetX?: number; offsetY?: n
const rect = canvas.getBoundingClientRect();
const scaleX = w / rect.width;
const scaleY = h / rect.height;
const mx = (e.offsetX !== undefined ? e.offsetX : e.clientX - rect.left) * scaleX;
const my = (e.offsetY !== undefined ? e.offsetY : e.clientY - rect.top) * scaleY;
const mx = (e.offsetX !== undefined ? e.offsetX : (e.clientX ?? 0) - rect.left) * scaleX;
const my = (e.offsetY !== undefined ? e.offsetY : (e.clientY ?? 0) - rect.top) * scaleY;
let cursor = 'default';
let newHoverIdx = -1;
let newHoverHit = null;
let newHoverHit: string | null = null;
// Selected rect takes priority for hover so edges stay reachable under overlaps
const selIdx = patternEditorSelectedIdx;
const hoverOrder = [];
const hoverOrder: number[] = [];
if (selIdx >= 0 && selIdx < patternEditorRects.length) hoverOrder.push(selIdx);
for (let i = patternEditorRects.length - 1; i >= 0; i--) {
if (i !== selIdx) hoverOrder.push(i);