Files
wled-screen-controller-mixed/CLAUDE.md
alexei.dolgolyov bd8d7a019f Codebase review: stability, performance, usability, and i18n fixes
Stability:
- Fix race condition: set _is_running before create_task in target processors
- Await probe task after cancel in wled_target_processor
- Replace raw fetch() with fetchWithAuth() across devices, kc-targets, pattern-templates
- Add try/catch to showTestTemplateModal in streams.js
- Wrap blocking I/O in asyncio.to_thread (picture_targets, system restore)
- Fix dashboardStopAll to filter only running targets with ok guard

Performance:
- Vectorize fire effect spark loop with numpy in effect_stream
- Vectorize FFT band binning with cumulative sum in analysis.py
- Rewrite pixel_processor with vectorized numpy (accept ndarray or list)
- Add httpx.AsyncClient connection pooling with lock in wled_provider
- Optimize _send_pixels_http to avoid np.hstack allocation in wled_client
- Mutate chart arrays in-place in dashboard, perf-charts, targets
- Merge dashboard 2-batch fetch into single Promise.all
- Hoist frame_time outside loop in mapped_stream

Usability:
- Fix health check interval load/save in device settings
- Swap confirm modal button classes (No=secondary, Yes=danger)
- Add aria-modal to audio/value source editors, fix close button aria-labels
- Add modal footer close button to settings modal
- Add dedicated calibration LED count validation error keys

i18n:
- Replace ~50 hardcoded English strings with t() calls across 12 JS files
- Add 50 new keys to en.json, ru.json, zh.json
- Localize inline toasts in index.html with window.t fallback
- Add data-i18n to command palette footer
- Add localization policy to CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 12:12:37 +03:00

9.1 KiB

Claude Instructions for WLED Screen Controller

CRITICAL: Git Commit and Push Policy

🚨 NEVER CREATE COMMITS WITHOUT EXPLICIT USER APPROVAL 🚨

🚨 NEVER PUSH TO REMOTE WITHOUT EXPLICIT USER APPROVAL 🚨

Strict Rules

  1. DO NOT create commits automatically after making changes
  2. DO NOT commit without being explicitly instructed by the user
  3. DO NOT push to remote repository without explicit instruction
  4. ALWAYS WAIT for the user to review changes and ask you to commit
  5. ALWAYS ASK if you're unsure whether to commit

Workflow

  1. Make code changes as requested
  2. STOP - Inform user that changes are complete
  3. WAIT - User reviews the changes
  4. ONLY IF user explicitly says "commit" or "create a commit":
    • Stage the files with git add
    • Create the commit with a descriptive message
    • STOP - Do NOT push
  5. ONLY IF user explicitly says "push" or "commit and push":
    • Push to remote repository

What Counts as Explicit Approval

YES - These mean you can commit:

  • "commit"
  • "create a commit"
  • "commit these changes"
  • "git commit"

YES - These mean you can push:

  • "push"
  • "commit and push"
  • "push to remote"
  • "git push"

NO - These do NOT mean you should commit:

  • "that looks good"
  • "thanks"
  • "perfect"
  • User silence after you make changes
  • Completing a feature/fix

Example Bad Behavior (DON'T DO THIS)

❌ User: "Fix the MSS engine test issue"
❌ Claude: [fixes the issue]
❌ Claude: [automatically commits without asking]  <-- WRONG!

Example Good Behavior (DO THIS)

✅ User: "Fix the MSS engine test issue"
✅ Claude: [fixes the issue]
✅ Claude: "I've fixed the MSS engine test issue by adding auto-initialization..."
✅ [WAITS FOR USER]
✅ User: "Looks good, commit it"
✅ Claude: [now creates the commit]

IMPORTANT: Auto-Restart Server on Code Changes

Whenever server-side Python code is modified (any file under /server/src/ excluding /server/src/wled_controller/static/), automatically restart the server so the changes take effect immediately. Do NOT wait for the user to ask for a restart.

No restart needed for frontend-only changes. Files under /server/src/wled_controller/static/ (HTML, JS, CSS, JSON locale files) are served directly by FastAPI's static file handler — changes take effect on the next browser page refresh without restarting the server.

Restart procedure

Use the PowerShell restart script — it reliably stops only the server process and starts a new detached instance:

powershell -ExecutionPolicy Bypass -File "c:\Users\Alexei\Documents\wled-screen-controller\server\restart.ps1"

Do NOT use Stop-Process -Name python (kills unrelated Python processes like VS Code extensions) or bash background & jobs (get killed when the shell session ends).

Default Config & API Key

The server configuration is in /server/config/default_config.yaml. The default API key for development is development-key-change-in-production (label: dev). The server runs on port 8080 by default.

Project Structure

This is a monorepo containing:

  • /server - Python FastAPI backend (see server/CLAUDE.md for detailed instructions)
  • /client - Future frontend client (if applicable)

Working with Server

For detailed server-specific instructions (restart policy, testing, etc.), see:

  • server/CLAUDE.md

UI Conventions for Dialogs

Hints

Every form field in a modal should have a hint. Use the .label-row wrapper with a ? toggle button:

<div class="form-group">
    <div class="label-row">
        <label for="my-field" data-i18n="my.label">Label:</label>
        <button type="button" class="hint-toggle" onclick="toggleHint(this)" title="?">?</button>
    </div>
    <small class="input-hint" style="display:none" data-i18n="my.label.hint">Hint text</small>
    <input type="text" id="my-field">
</div>

Add hint text to both en.json and ru.json locale files using a .hint suffix on the label key.

Select dropdowns

Do not add placeholder options like -- Select something --. Populate the <select> with real options only and let the first one be selected by default.

Modal dirty check (discard unsaved changes)

Every editor modal must have a dirty check so closing with unsaved changes shows a "Discard unsaved changes?" confirmation. Use the Modal base class pattern from js/core/modal.js:

  1. Subclass Modal with snapshotValues() returning an object of all tracked field values:

    class MyEditorModal extends Modal {
        constructor() { super('my-modal-id'); }
        snapshotValues() {
            return {
                name: document.getElementById('my-name').value,
                // ... all form fields
            };
        }
        onForceClose() {
            // Optional: cleanup (reset flags, clear state, etc.)
        }
    }
    const myModal = new MyEditorModal();
    
  2. Call modal.snapshot() after the form is fully populated (after modal.open()).

  3. Close/cancel button calls await modal.close() — triggers dirty check + confirmation.

  4. Save function calls modal.forceClose() after successful save — skips dirty check.

  5. For complex/dynamic state (filter lists, schedule rows, conditions), serialize to JSON string in snapshotValues().

The base class handles: isDirty() comparison, confirmation dialog, backdrop click, ESC key, focus trapping, and body scroll lock.

Card appearance

When creating or modifying entity cards (devices, targets, CSS sources, streams, audio/value sources, templates), always reference existing cards of the same or similar type for visual consistency. Cards should have:

  • Clone (📋) and Edit (✏️) icon buttons in .template-card-actions
  • Delete (✕) button as .card-remove-btn
  • Property badges in .stream-card-props with emoji icons
  • Crosslinks: When a card references another entity (audio source, picture source, capture template, PP template, etc.), make the property badge a clickable link using the stream-card-link CSS class and an onclick handler calling navigateToCard(tab, subTab, sectionKey, cardAttr, cardValue). Only add the link when the referenced entity is found (to avoid broken navigation). Example: <span class="stream-card-prop stream-card-link" onclick="event.stopPropagation(); navigateToCard('streams','audio','audio-multi','data-id','${id}')">🎵 Name</span>

Use icon-only buttons (✓ / ✕) matching the device settings modal pattern, not text buttons:

<div class="modal-footer">
    <button class="btn btn-icon btn-secondary" onclick="closeMyModal()" title="Cancel" data-i18n-title="settings.button.cancel" data-i18n-aria-label="aria.cancel">&#x2715;</button>
    <button class="btn btn-icon btn-primary" onclick="saveMyEntity()" title="Save" data-i18n-title="settings.button.save" data-i18n-aria-label="aria.save">&#x2713;</button>
</div>

Slider value display

For range sliders, display the current value inside the label (not in a separate wrapper). This keeps the value visible next to the property name:

<label for="my-slider"><span data-i18n="my.label">Speed:</span> <span id="my-slider-display">1.0</span></label>
...
<input type="range" id="my-slider" min="0" max="10" step="0.1" value="1.0"
    oninput="document.getElementById('my-slider-display').textContent = this.value">

Do not use a range-with-value wrapper div.

Tutorials

The app has an interactive tutorial system (static/js/features/tutorials.js) with a generic engine, spotlight overlay, tooltip positioning, and keyboard navigation. Tutorials exist for:

  • Getting started (header-level walkthrough of all tabs and controls)
  • Per-tab tutorials (Dashboard, Targets, Sources, Profiles) triggered by ? buttons
  • Device card tutorial and Calibration tutorial (context-specific)

When adding new tabs, sections, or major UI elements, update the corresponding tutorial step array in tutorials.js and add tour.* i18n keys to all 3 locale files (en.json, ru.json, zh.json).

Localization (i18n)

Every user-facing string must be localized. Never use hardcoded English strings in showToast(), error.textContent, modal messages, or any other UI-visible text. Always use t('key') from ../core/i18n.js and add the corresponding key to all three locale files (en.json, ru.json, zh.json).

  • In JS modules: import { t } from '../core/i18n.js'; then showToast(t('my.key'), 'error')
  • In inline <script> blocks (where t() may not be available yet): use window.t ? t('key') : 'fallback'
  • In HTML templates: use data-i18n="key" for text content, data-i18n-title="key" for title attributes, data-i18n-aria-label="key" for aria-labels
  • Keys follow dotted namespace convention: feature.context.description (e.g. device.error.brightness, calibration.saved)

General Guidelines

  • Always test changes before marking as complete
  • Follow existing code style and patterns
  • Update documentation when changing behavior
  • Write clear, descriptive commit messages when explicitly instructed
  • Never make commits or pushes without explicit user approval