dyadmisha
синхронизированные коммиты с main на dyadmisha/dune-weaver из зеркала
510f20009a Fix thread-unsafe direct serial access bypassing connection lock
Replace all state.conn.ser direct access with the properly locked
SerialConnection abstraction methods (send, reset_input_buffer).
The unlocked writes caused race conditions that corrupted Bluetooth
serial connections, leading to unrecoverable I/O errors (errno 5).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5a8083d14a feat: Complete React UI rewrite with new frontend architecture (#112)
* Add homing blocking - prevent movement during homing
Backend changes:
- Add is_homing flag to AppState
- Set flag during homing operation with proper cleanup on success/failure
- Add check_homing_in_progress() helper function
- Block these endpoints while homing: run_theta_rho, run_theta_rho_file,
run_playlist, move_to_center, move_to_perimeter, send_coordinate
- Add is_homing to status response for frontend awareness
- Return HTTP 409 Conflict if action attempted during homing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix led
* Add root package.json and package-lock.json
- Update .gitignore to only ignore state.json instead of all *.json
- Add package.json with npm scripts for development
- Add package-lock.json for reproducible installs
npm scripts available:
- npm run dev: Start frontend and backend concurrently
- npm run build: Build React frontend
- npm run start: Build and start server
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Ignore additional JSON config files
- playlists.json (runtime data)
- metadata_cache.json (generated cache)
- tsconfig.json (TypeScript config - use frontend's)
- dune-weaver-touch/*.json (touch app configs)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Enable network access for Vite dev server
Add --host flag to dev and preview scripts so the frontend
is accessible from other machines on the network (useful for
remote development on Pi)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Allow all hosts for Vite dev server
Enables access via hostnames like dwg.local on local network
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Mount metadata_cache.json for persistence
Without this mount, the metadata cache was regenerated on every
container restart because the file was lost inside the container.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert to mounting entire app directory
Mounting individual files caused issues with metadata_cache.json
not being created properly. The .:/app mount is simpler and ensures
all runtime files (state.json, metadata_cache.json, playlists/, etc.)
are persisted automatically.
Also fixed dbus socket path typo.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add settings enhancements and Docker update functionality
- Add preferred port selection with auto-connect disable option
- Display gear ratio and X/Y steps per mm in machine settings
- Add MQTT test connection button to Home Assistant section
- Change "WLED" to "LED" in Still Sands section
- Implement Docker-based software update for two-container setup
- Update button now always enabled for testing
- Fix sensor offset input to allow clearing value
- Add searchable select component
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve settings, homing auto-detection, and update flow
Settings & UI:
- Move timezone from Still Sands to Machine Settings (system-wide)
- Add UTC offset timezones (UTC-12 to UTC+12) to selector
- Remove update button, show CLI instructions instead
- Remove "Paused" badge from Now Playing bar
- Logs now display timestamps in configured timezone
Homing:
- Auto-detect sensor homing from FluidNC $22 setting
- Add homing_user_override flag to respect explicit user preference
- Only auto-set sensor mode if user hasn't configured otherwise
Update system:
- Add git pull to update_manager for local file updates
- Remove non-existent frontend image pull
- Handle container self-restart gracefully
- Direct users to use 'dw update' from host for full updates
Bug fixes:
- Validate auto-play playlist exists before running
- Clear invalid playlist references from state automatically
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Consolidate cache-all-previews logic and fix Docker restart
- Extract shared cacheAllPreviews() function to previewCache.ts
- Simplify BrowsePage and Layout modal to use shared function
- Fix PlaylistsPage to validate previews before caching
- Fix Docker restart using correct container name (dune-weaver-backend)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add timeout protection to cache generation
Prevents backend from hanging when parsing problematic pattern files.
Files that take longer than 30 seconds to parse are skipped with an error log.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix log handler performance and restore Still Sands timezone
- Remove expensive timezone conversion from log handler that created
ZoneInfo objects on every log message (major Pi performance issue)
- Logs now use simple local system time
- Move timezone setting back to Still Sands where it's actually needed
- Remove unused timezone from Machine Settings
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Switch Docker images to feature-react-ui branch
TODO: Revert to main before merging to main branch.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix preview batch requests overwhelming backend on Pi
Both BrowsePage and PlaylistsPage were sending all uncached pattern
previews in a single request, causing:
- 100+ second processing times for 879 files
- 504 gateway timeouts on other API calls
- 8.9MB responses causing nginx buffering warnings
Now batches requests into groups of 10 with 100ms delays between
batches, matching the cacheAllPreviews behavior.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve Now Playing bar UX and timezone input
Now Playing bar:
- Fix race condition where auto-collapse undid expansion on playlist start
- Listen to playback-started event for more reliable expansion
- Add 500ms delay before auto-collapse to prevent race conditions
- Click preview image in mini view to expand
- Click canvas in expanded view to collapse back
Settings (Still Sands timezone):
- Replace Select with Input + datalist for custom timezone support
- Add UTC offset options (UTC-12 to UTC+12)
- Users can now type any IANA timezone (e.g., Asia/Kolkata)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve Settings page UX and fix Auto Play issues
Auto Play on Boot:
- Fix playlist fetch (backend returns array directly, not object)
- Add pause time unit selector (sec/min/hr) matching Playlists page
- Smart conversion: 300s displays as "5 min", saves back as seconds
Still Sands timezone:
- Replace Select with Input + datalist for custom timezone support
- Add UTC offset options (UTC-12 to UTC+12)
- Users can type any IANA timezone
UI polish:
- Increase spacing between labels and inputs (space-y-2 → space-y-3)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add serial debug terminal, background homing, and UI improvements
- Fix Python 3.9 compatibility for type hints in scheduling.py and process_pool.py
- Change startup order: backend starts immediately, homing runs in background
- Add homing progress overlay with streaming logs, 5s countdown after completion
- Prevent movement commands (home, center, perimeter, align) while pattern running
- Add serial debug terminal tab for raw command communication without full connection
- Make logs drawer resizable with drag handle
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add multi-table control support with mDNS discovery
Backend:
- Add CORS middleware to enable cross-origin requests from other frontends
- Add table identity endpoints (GET/PATCH /api/table-info) with UUID and name
- Add mDNS advertisement using zeroconf (_duneweaver._tcp.local.)
- Add discovery endpoint (GET /api/discover-tables) to find tables on LAN
Frontend:
- Add centralized API client (apiClient.ts) with configurable base URL
- Add TableContext for multi-table state management with localStorage persistence
- Add TableSelector component in header for switching between tables
- Update WebSocket connections to use apiClient for dynamic URL targeting
- Refactor key fetch calls to use apiClient
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Move serial terminal from logs drawer to Table Control page
- Relocate serial terminal UI and logic from Layout to TableControlPage
for better contextual placement with other table controls
- Add auto-connect feature that detects main connection port
- Update LED pixel order default from GRB to RGB (WS2815/WS2811)
- Use apiClient for consistent WebSocket URL construction
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix nginx upstream resolution for host network mode
Use 127.0.0.1:8080 instead of backend:8080 since frontend runs
with network_mode: host, which disables Docker DNS resolution.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix version_manager method call in get_table_info endpoint
Use await get_current_version() instead of non-existent get_version().
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix remaining get_version() calls to use get_current_version()
- modules/core/mdns.py: Fix mDNS advertisement (2 occurrences)
- main.py: Fix discover_tables endpoint
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix DW LEDs race condition and mDNS async warning
- Fix DW LEDs not working after background homing: connect_device() was
overwriting led_controller with None because it didn't handle "dw_leds"
provider. Now preserves existing controller or initializes if needed.
- Use AsyncZeroconf in discover_tables() to fix blocking I/O warning
- Add frontend dist volume mount for dev without rebuild
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix DW LEDs not initializing on application restart
The DW LED controller uses lazy hardware initialization, but startup code
only created the LEDInterface without triggering initialization. Now calls
check_status() and effect_idle() on startup, matching /set_led_config behavior.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix DW LEDs staying on green blink after connection
The effect_idle() function was doing nothing when no idle effect was
configured, causing the green "connected" blink to persist. Now defaults
to Rainbow effect using controller's current speed/intensity parameters.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use controller's current parameters for default Rainbow idle effect
Explicitly pass speed, intensity, and colors from controller state
instead of relying on implicit values.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove static/dist volume mount causing nginx 403 on Pi
The mount overrides the built-in frontend with local directory that
may not exist on Pi, causing 403 Forbidden errors.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove mDNS implementation and zeroconf dependency
- Remove zeroconf from requirements.txt
- Remove mdns module import and startup/shutdown code
- Remove /api/discover-tables endpoint
- Delete modules/core/mdns.py
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix remote table control and improve multi-table stability
- Convert 56+ direct fetch() calls to apiClient across all pages
- BrowsePage, LEDPage, SettingsPage, PlaylistsPage, TableControlPage
- previewCache, Layout, NowPlayingBar
- Add apiClient.delete() body support for DELETE requests with payload
- Fix table selection persistence across page refreshes
- Use ref to track restored selection before async state updates
- Reload page on table switch for clean WebSocket/cache state
- Fix WebSocket reconnection bugs in Layout and NowPlayingBar
- Add shouldReconnect flag to prevent stale onclose handlers
- Clear pending reconnect timeouts properly
- Optimize NowPlayingBar preview fetching
- Skip fetch when bar is hidden
- Track last fetched files to prevent duplicate requests
- Fix DW LED default Rainbow speed to 60 for smoother animation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve mobile UI layout and fix pattern stop hanging
Mobile UI improvements:
- Add hamburger menu for header actions on mobile
- Make navbar fixed with safe area insets for iOS
- Use dynamic viewport height (dvh) for proper mobile sizing
- Compact page headers (text-xl on mobile, text-3xl on desktop)
- Improve Playlists page controls layout with stacking
- Make table selector and playlist actions visible on mobile (no hover)
- Compact Browse page filters (search + category on same row)
- Reduce Serial Terminal header overflow on mobile
Pattern execution fix:
- Add stop request checks in motion thread to prevent hanging
- Add 10s timeout on pattern lock acquisition in stop_actions
- Add 30s overall timeout in motion command retry loop
- Clear motion queue when stop is requested
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix Safari mobile WebSocket issues and improve request handling
- Fix WebSocket race condition causing "closed before established" error
on Safari mobile by assigning wsRef immediately after creation
- Bypass Vite WebSocket proxy in dev mode for Safari mobile compatibility
(connect directly to port 8080 instead of proxying through 5173)
- Add AbortController to cancel in-flight preview requests on page
navigation (BrowsePage, PlaylistsPage)
- Fix cache manager race condition with asyncio.Lock for metadata writes
- Add debouncing (300ms) to LED color picker to reduce API spam
- Remove 30s motion thread timeout while preserving force stop capability
- Add Reset button (Ctrl+X) to serial terminal section
- Remove table discovery refresh button from TableSelector
- Fix TableContext reload loop on initial table selection
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix TypeScript build error for NodeJS.Timeout type
Use ReturnType<typeof setTimeout> instead of NodeJS.Timeout for
browser compatibility in Docker build environment.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix CPU spike on pattern start by moving blocking I/O to thread
- Wrap parse_theta_rho_file() with asyncio.to_thread() in run_theta_rho_file()
- Wrap load_metadata_cache() with asyncio.to_thread() in run_theta_rho_files()
This prevents the event loop from blocking during file parsing, keeping
WebSocket updates, LED control, and status broadcasts responsive while
large pattern files are being read.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove timeout from motion thread 'ok' response waiting
The motion thread now waits indefinitely for hardware 'ok' response,
only aborting on stop_requested. Use force stop if hardware becomes
unresponsive. Exception-based retries still in place for connection errors.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert motion thread changes from 1b02b8c that caused pattern hangs
Reverts _execute_move() and _send_grbl_coordinates_sync() to their
pre-commit state. The stop_requested checks added in that commit
were interfering with normal pattern playback.
The motion thread now uses the simpler, working logic:
- _execute_move: Only checks self.running, not stop_requested
- _send_grbl_coordinates_sync: Simple retry loop without stop checks
Stop functionality still works via stop_actions() setting state flags
that are checked in the main pattern execution loop.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* add stop check
* Clear pause time between patterns on stop
Always clear pause_time_remaining and original_pause_time when stopping,
not just when clearing the playlist. This ensures the UI doesn't show
stale countdown values after stopping.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add historical ETA, persist pre-execution selection, fix mobile UI
- Display historical execution time as ETA when pattern was previously
completed at the same speed (shows history icon indicator)
- Persist pre-execution action selection to localStorage (shared between
Browse and Playlists pages)
- Fix delete pattern 404 by adding endpoint to Vite proxy config
- Fix mobile UI: circular preview containers, progress bar spacing
- Remove marquee effect from pattern name display
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix dark mode color hierarchy for better visual distinction
- Increase border color from 25% to 32% lightness for visibility
- Increase muted/secondary/accent from 20% to 25% lightness
- Increase input background from 22% to 28% lightness
- Remove nested bg-card from inner containers (use border only)
- Use bg-muted/50 for info cards and stat displays
- Fix Alert component using bg-background (too dark inside cards)
- Fix Still Sands time period cards background
- Fix LED page effect preview boxes background
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix process pool semaphore leaks and WebSocket connection warnings
Backend:
- Add shutdown flag to prevent race conditions in process pool
- Use wait=True in signal handler to allow workers to release semaphores
Frontend:
- Fix WebSocket cleanup to avoid closing CONNECTING sockets
- Add onopen handlers to gracefully close orphaned connections
- Properly handle React StrictMode double-mounting
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix "Up Next" showing wrong pattern during clear pattern execution
When a clear pattern was running for pattern 1, "Up Next" incorrectly
showed pattern 2 instead of pattern 1. The issue had two parts:
1. is_clearing flag was immediately reset to False by stop_actions()
called inside _execute_pattern_internal(), before the pattern started
2. get_status() calculated next_file as playlist[current_index + 1]
regardless of whether a clear pattern was running
Fixed by:
- Preserving is_clearing flag around stop_actions() call
- Setting is_clearing = True before clear pattern, False after
- Modifying get_status() to return current main pattern as next_file
when is_clearing is True
- Adding is_clearing to status response for frontend awareness
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve NowPlayingBar UI: spacing, mobile queue access, drag-and-drop
- Fix remaining time too close to progress bar by increasing width
from w-16 to w-20 when showing historical ETA and gap to 1.5
- Add queue button to header on mobile for easy queue access
- Refactor queue dialog to use drag-and-drop reordering with dnd-kit
- Replace up/down arrow buttons with drag handles for more intuitive UX
- Remove "hide clear patterns" toggle (no longer needed since playlists
now only contain main patterns)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* simplify playlist playback
* Reduce default process pool workers for low-RAM devices
Default to 1 worker instead of (cpu_count - 1) to conserve RAM on
memory-constrained devices like Pi Zero 2 W (512MB). This prevents
memory pressure that causes UI sluggishness and unresponsive stops.
Added POOL_WORKERS env var to override if more workers are needed
on systems with more RAM.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix stale "Waiting for next pattern" UI after skipping pause
Clear pause_time_remaining and original_pause_time when a new pattern
starts executing, not just after the pause loop ends. This prevents
stale waiting state from showing in the UI if skip was triggered
during a pause period.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix stop not interrupting motion thread retry loop
The motion thread's retry loop was checking stop_requested only before
sending commands, not at the start of each iteration or while waiting
for responses. This caused infinite "No 'ok' received" retries when
stop was requested.
Added stop checks at:
- Start of each retry iteration (immediate return)
- Inside response wait loop (escape blocking readline)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Bump version to 4.0.0
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Auto-create playlist if not found on get_playlist
Instead of returning 404 when a playlist doesn't exist, automatically
create an empty playlist. This handles cases like "Favorites" where
the UI expects the playlist to exist.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix status indicator not syncing when switching tables
Reset isConnected and isBackendConnected to false when switching tables
so the header status indicator shows the transitional state until the
new table's WebSocket connection reports its actual status.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve UI responsiveness, auto-connect options, and LED stability
Frontend:
- Fix Select dropdowns overflowing viewport (add max-height + scroll)
- Make Custom Logo upload button stack on mobile
- Add all LED color order options (BGR, RBG, GBR, BRG) with grouping
- Fix LED page icon alignment and mobile speed/intensity layout
- Redesign mobile filter bar (compact single row)
- Rename "Uncategorized" to "Default Patterns"
Backend:
- Add auto-connect disabled mode (__none__ option)
- Deprioritize /dev/ttyS0 during auto-connect (still available manually)
- Fix LED hardware change detection using wrong defaults
- Add 500ms delay between LED controller stop/reinit for stability
- Change default pixel order to RGB (WS2815) and GPIO to 18
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Auto-save auto-connect setting on dropdown change
- Remove need to click Save button after selecting auto-connect option
- Setting now saves immediately when dropdown value changes
- Shows toast confirmation with selected option
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix auto-connect settings and SelectLabel error
- Use explicit string values for preferred_port (__auto__, __none__)
- Backend stores values directly instead of converting to null
- Fix SelectLabel must be within SelectGroup error
- Connection manager handles both __auto__ and null for backwards compat
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Redesign playlist controls and improve button styling
- Convert playlist controls to floating pill-shaped element with blur backdrop
- Change all outline buttons to secondary variant across app
- Enhance secondary button hover with scale, shadow, and accent color
- Highlight clear pattern icon when a pattern is selected
- Add swap icon to pause time to indicate unit is clickable
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add pattern history display, queue management, and multi-table UX improvements
- Add pattern execution history showing last run time and speed in browse panel
- Add "Play Next" and "Add to Queue" buttons to insert patterns into running playlist
- Replace custom slide-in panel with shadcn Sheet component for better UX
- Show table name in header when multiple tables are connected
- Fix CORS configuration for cross-origin multi-table access
- Cache parsed coordinates in state to avoid re-parsing large files on Pi Zero 2W
- Use thread executor instead of process pool for coordinate parsing (reduces memory pressure)
- Add Now Playing button to desktop header for quick access
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve mobile responsiveness and unify page styling
- Match navbar background with header (bg-card)
- Reduce page title font size and add left padding
- Make filter bar compact on mobile with icon-only buttons
- Standardize page spacing across all pages (py-3 sm:py-6)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add play time badges, editable numeric inputs, and UI refinements
- Add play time badges on pattern cards showing last execution duration
- Make Speed, Intensity, Number of LEDs, and Pause inputs editable via typing
- Replace timezone datalist with SearchableSelect for better UX
- Add soft reset button to Control page (sends GRBL Ctrl+X)
- Change Home button to primary (blue) and Reset to secondary styling
- Reduce Select dropdown max-height from 384px to 256px
- Add /soft_reset and /api/pattern_history_all backend endpoints
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix playlist page layout and silence auto-connect toast
- Fix playlist container height to account for bottom navbar
- Align sidebar and main content headers with matching structure
- Add playlist count subtitle to sidebar header
- Make serial terminal auto-connect silent (no toast on page load)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* update
* Fix TypeScript error in serial connect button onClick handler
Wrap handleSerialConnect in arrow function to prevent MouseEvent being
passed as the silent parameter, which caused a type mismatch during build.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix stuck pattern state and improve stop/reset reliability
- Add timeout (30s) to check_idle_async() to prevent infinite loops
- check_idle_async() now respects stop_requested flag for early exit
- Add /force_stop endpoint for nuclear cleanup when normal stop fails
- /stop_execution now returns error on timeout, triggering force_stop fallback
- /soft_reset uses direct serial write for more reliable reset
- Reset button calls force_stop first to clear stuck state
- Stop buttons in UI auto-retry with force_stop on failure
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert backend changes to debug serial terminal issue
Temporarily reverting stop/reset reliability changes to test if they
caused the serial terminal to stop receiving responses.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Prevent crash when sensor homing partially fails
Skip position zeroing (x0 y0) if not both X and Y axes received homing
confirmation. Moving to zero with an unknown axis position would crash
the machine - essentially performing unintended crash homing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add force stop endpoint and improve pattern execution control
- Add /force_stop endpoint to clear all pattern state when normal stop times out
- Improve soft_reset to send Ctrl+X directly via serial for reliability
- Add timeout parameter to check_idle_async with stop request handling
- Fix homing to skip zeroing only when X homed but Y failed (avoids Y crash)
- Return success/failure from stop_actions for better error handling
- Change default LED speed from 128 to 50
- Remove "(common)" labels from LED pixel order options
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix WebSocket race condition on page load for multi-table support
Initialize apiClient baseUrl synchronously from localStorage before any
React components mount. This prevents WebSockets from briefly connecting
to the wrong backend when a remote table was previously selected.
Also skip notifying listeners when setBaseUrl is called with the same
URL to avoid unnecessary WebSocket reconnections.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Move auto-homing to after pause time in playlist execution
Relocate the auto-homing check from immediately after pattern completion
to after the pause time, right before the next clear pattern starts.
This allows users to enjoy the completed pattern during the full pause
duration before homing disrupts it. The homing now logically precedes
the clear pattern which erases the sand anyway.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix patterns not loading due to stale localStorage URL initialization
CRITICAL FIX: Reverted apiClient localStorage pre-initialization that
could cause all API calls to silently fail if users had stale table
data pointing to unreachable servers.
Also includes:
- Increase main content top padding (pt-16 → pt-[4.5rem]) to prevent
header overlap on mobile
- Make Add Pattern and Cache buttons responsive (h-9 on mobile)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix nested button HTML error in PatternCard
Change favorite toggle from <button> to <span role="button"> to avoid
invalid nested button elements. Adds proper keyboard accessibility
with tabIndex and onKeyDown handlers for Enter/Space keys.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix race condition with stale serial port from wrong backend
When using multi-table mode, the frontend could receive serial port data
from a different backend during the brief window before TableContext
validates the active table URL. This caused connect attempts with
non-existent ports (e.g., Mac's /dev/cu.usbserial on a Pi).
Fix: Validate that the port returned from /serial_status exists in the
available ports list from /list_serial_ports before setting it. If the
port doesn't exist on this machine, log a warning and ignore it.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix move_to_center/perimeter not working after pattern stop
When a pattern was stopped, state.stop_requested remained True. The
motion control thread checks this flag and aborts commands immediately,
causing manual move commands (center/perimeter) to return 200 OK but
never actually send G-code to the controller.
Fix: Clear stop_requested at the start of move_polar() so manual
movement commands always execute regardless of previous stop state.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix SelectItem highlight not matching pill-shaped dropdown
Changed SelectItem border-radius from rounded-sm to rounded-xl to match
the pill-shaped aesthetic of SelectTrigger (rounded-full) and
SelectContent (rounded-2xl). The highlight now properly fills the item.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add event-driven stop/skip for instant interrupt response
Previously, stop and skip requests during scheduled pauses were ignored
or delayed up to 1 second due to polling-based checks. This caused the
UI to appear frozen when users tried to skip or stop during Still Sands
periods.
Changes:
- Add asyncio.Event backing to stop_requested/skip_requested flags
- Add wait_for_interrupt() method for instant event-driven waiting
- Fix in-pattern pause loop to respond to stop/skip immediately
- Fix post-pattern scheduled pause to respond to skip (not just stop)
- Increase DEFAULT_WORKERS from 1 to 3 for better parallelism
- Remove deprecated .cursorrules file (replaced by .claude/CLAUDE.md)
The property setters automatically sync flags and events, so existing
code using state.stop_requested = True works unchanged while async
code now gets instant wake-up via events.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix stop timeout during pause and stale "waiting" UI state
Two issues fixed:
1. Stop/skip during manual pause could hang indefinitely:
- The event wait had no timeout, relying solely on asyncio.Event
- If stop was called from sync context (no event loop), events weren't set
- Added 1-second timeout to ensure flags are polled as fallback
2. "Waiting for next pattern" shown during pattern playback:
- Single pattern execution didn't clear pause_time_remaining
- Stale values from previous playlist caused incorrect UI state
- Now cleared at start of run_theta_rho_file()
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve UI consistency and responsiveness
- Browse page: Add gap between icons and text in filter dropdowns
- NowPlayingBar: Make real-time preview responsive (42vh on desktop,
capped at 500px), increase vertical padding
- PlaylistsPage: Restyle pattern picker modal filters to match Browse
page (pill-shaped buttons, icon-only on mobile, consistent order:
folder → sort → direction)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add host-based update watcher for Docker deployments
Implements a signal-based update mechanism that allows triggering
`dw update` from within Docker containers. The container creates a
trigger file that a host systemd service watches for, then executes
the full update process on the host machine.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add Update Now button to Settings page
Adds a button to trigger software updates from the web UI instead of
requiring SSH access to run `dw update` manually. Shows status feedback
during update process.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve Now Playing Bar positioning and add favorite to pattern panel
- Fix Now Playing Bar position calculation when logs drawer is open
- Pass dynamic logsDrawerHeight instead of hardcoded value
- Add vertical padding to collapsed bar content
- Adjust bar heights for better spacing
- Add favorite toggle button in pattern details panel header
- Add env vars for testing update functionality
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix docker-compose.yml invalid empty environment mapping
Move environment block to comments since all values were commented out.
An empty environment: key with only comments is invalid YAML.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix Python 3.9 compatibility for type hints
Use typing.Optional and typing.Tuple instead of Python 3.10+ union
syntax (str | None) for backwards compatibility.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert auto-update feature, instruct users to SSH and run dw update
The host-based update watcher added complexity and required manual
service installation. Simpler to just tell users to SSH in and run
the dw update command directly.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix dw update to re-exec with new CLI after git pull
The script now re-executes itself after updating the dw CLI file,
ensuring any new update logic in the script actually runs. Uses
--continue flag to skip the pull phase on re-exec.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add sort by favorites option to Browse and Playlist pages
- Add 'favorites' to SortOption type in lib/types.ts
- BrowsePage: Add favorites sorting (favorites first, then by name)
- PlaylistsPage: Add favorites state, load from Favorites playlist
- PlaylistsPage: Add favorites sorting to pattern picker modal
- Both pages now show Favorites as first sort option in dropdown
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Change header Reset button to do table soft reset instead of Docker restart
The Reset button (restart_alt icon) in the header now:
- Calls /force_stop to clear any stuck state
- Calls /soft_reset to reset the DLC32/ESP32
- Shows "Reset sent. Homing required." toast
This matches the behavior of the Reset button in TableControlPage.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add missing endpoints to nginx proxy config
Added force_stop, soft_reset, reorder_playlist, and delete_theta_rho_file
to the nginx proxy regex. These were missing, causing 404s when accessing
a remote table through nginx.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert header button to Docker restart, remove TableControl Reset button
- Layout.tsx: Revert handleRestart to restart Docker containers (with confirmation)
- Layout.tsx: Change labels back to "Restart Docker"
- TableControlPage.tsx: Remove Reset button (keep only Home and Stop)
- TableControlPage.tsx: Remove handleSoftReset function
- TableControlPage.tsx: Remove client-side check blocking Home button
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix clipboard copy for non-HTTPS contexts (http://dwg.local)
Add copyToClipboard helper that falls back to document.execCommand('copy')
when navigator.clipboard is unavailable (non-secure HTTP contexts).
Fixes copy button in Homing Log and main logs drawer.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve UI consistency and mobile experience
- NowPlayingBar: Add pattern preview in expanded view, optimistic drag-drop
- NowPlayingBar: Show move to top/bottom buttons always on mobile
- Layout: Unify desktop/mobile header with same 3-icon layout (play, table, menu)
- Layout: Tighter icon spacing, fix active table name display
- TableSelector: Remove dropdown arrow, move checkmark to far right
- BrowsePage: Add swipe to dismiss for pattern detail sheet
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* minor ui change
* Add PWA support and improve mobile experience
- Add vite-plugin-pwa for service worker and caching
- Add iOS safe area support (Dynamic Island, notch)
- Add mobile drill-down navigation for Playlists with swipe-back gesture
- Update favicon/PWA icons to square format (OS applies masks)
- Add circular favicons with transparent background for browser tabs
- Add dynamic manifest endpoint for custom branding support
- Generate proper PWA icons on custom logo upload
- Allow dismissing homing splash screen
- Add scroll-to-top on page navigation
- Enable header blur only on Browse page
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve UI with floating Now Playing button and various fixes
- Add centered floating Now Playing button above nav bar (all screens)
- Remove Now Playing button from header to declutter
- Fix table selector dropdown alignment with header pill
- Show cache progress percentage on mobile
- Fix toast notifications blocked by Dynamic Island in PWA mode
- Improve lazy loading for pattern previews in playlists and queue
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Show table icons in table selector dropdown
- Add customLogo field to Table interface
- Fetch settings when discovering/adding tables to get custom logo
- Refresh remote tables in background to fetch their logos
- Replace wifi icons with table's actual icon/logo
- Add status dot overlay to indicate online/offline state
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve Now Playing bar mobile UX and PWA safe areas
- Lock body scroll when Now Playing bar is visible
- Add swipe-to-dismiss for queue dialog with visual indicator
- Allow scrolling inside queue while preventing background scroll
- Add safe area padding for Dynamic Island in expanded view
- Position header buttons below Dynamic Island when expanded
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Persist added tables in backend for multi-device access
Backend:
- Add known_tables field to AppState for persistent storage
- Add GET/POST/DELETE/PATCH /api/known-tables endpoints
- Tables stored in state.json alongside other settings
Frontend:
- Fetch known tables from backend on discovery
- Persist added tables to backend via POST
- Remove tables from backend via DELETE
- Update known table names in backend for remote tables
- Keep localStorage for active table selection (client-specific)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix multi-table WebSocket connections failing in production
The issue was that table switching worked in development but failed in
production (port 80). Two problems were fixed:
1. Removed reliance on stale `isCurrent` flag from localStorage which
could incorrectly prevent the base URL from being set
2. Added URL normalization for comparison to handle port differences
(e.g., http://host:80 vs http://host are now correctly recognized
as the same origin)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add debug logging for multi-table WebSocket issue
Adds console.log statements to track:
- When setBaseUrl is called and with what values
- What WebSocket URLs are generated
- Table restoration from localStorage and URL comparison
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix race condition: pre-initialize apiClient base URL from localStorage
WebSocket connections were being established before TableContext had
a chance to set the base URL, causing connections to go to the wrong
server when switching tables.
Solution: Initialize the base URL synchronously at module load time,
before React renders. This ensures the correct URL is available
immediately when components mount and create WebSocket connections.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove debug logging from multi-table fix
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Center real-time preview canvas in expanded view
Added flex-1 to the canvas container so it expands to fill available
space, allowing the canvas to be properly centered.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Center real-time preview canvas in expanded Now Playing view
- Added h-full to max-width container for proper height inheritance
- Added flex-1 to canvas wrapper to fill available space
- Removed conflicting md:justify-center from inner container
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Prevent debug terminal from interfering with pattern execution
- Remove auto-connect to serial debug terminal on page load
- Add warning alert advising users not to use terminal during pattern execution
- Fixes silent pattern failure when debug terminal intercepts GRBL responses
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix build error and add retry logic for motion commands
- Remove unused mainConnectionPort state (fixes TS6133 build error)
- Add 1-second timeout with automatic resend for motion commands
- Motion thread now retries forever until 'ok' is received
- Prevents silent hang if response is missed
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Center warning text vertically in alert box
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add Reset button with confirmation dialog
- Sends Ctrl+X soft reset to controller
- Shows warning that homing is required after reset
- Uses existing /soft_reset API endpoint
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Increase serial port selector width to prevent text cutoff
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Replace native select with shadcn Select for serial port
- Fixes dropdown positioning issues with native select
- Uses Radix UI portal for proper z-index and positioning
- Auto-refreshes port list when dropdown opens
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert retry timeout - wait indefinitely for GRBL 'ok'
GRBL only sends 'ok' after a move completes, which can take many
seconds at slow speeds. The 1-second timeout was causing premature
resends and duplicate commands.
Reverted to original behavior: wait indefinitely for 'ok' response.
Added stop_requested checks so patterns can still be cancelled.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Restore null check in get_status_response()
Prevents crash when called without active connection.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix serial stability issues caused by asyncio event overhead
- Make stop_requested/skip_requested setters thread-safe using
call_soon_threadsafe() for cross-thread event manipulation
- Remove state.stop_requested=False from move_polar() which was
triggering event manipulation thousands of times per pattern
- Add stop_requested clearing to manual move endpoints instead
(/move_to_center, /move_to_perimeter, /send_coordinate)
- Fix BrowsePage upload refresh to call fetchPatterns()
The root cause was move_polar() setting stop_requested on every
coordinate, causing constant asyncio event manipulation that
created race conditions and overhead during pattern execution.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update package-lock.json dependencies
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix CPU spin loop in motion thread readline wait
Added sleep(0.01) after readline() to prevent 100% CPU usage when
readline() returns empty (timeout or bad serial state). Without this,
the inner while loop would spin continuously.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove dangerous lock=None in connection close methods
Setting self.lock = None after close() caused race conditions:
- Motion thread calls readline() with "with self.lock:"
- If close() was called, lock is None
- AttributeError is caught, motion thread retries forever
- Pattern hangs waiting for "ok" that never comes
This was a regression not present in main branch.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix UI showing stale countdown after skip during pause
Clear both pause_time_remaining and original_pause_time immediately
when skip breaks the pause loop. Previously original_pause_time wasn't
cleared until the next pattern started, causing the UI to show stale
countdown state.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add 120s timeout to motion thread readline loop
Previously the motion thread would wait indefinitely for 'ok' response.
If serial communication fails silently (no exception, just no data),
the pattern would hang forever.
Now after 120 seconds without 'ok', the motion thread:
- Logs an error about the timeout
- Sets stop_requested = True to stop the pattern
- Returns False to signal the failure
Also added logging for GRBL error/alarm responses for debugging.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix race condition with multiple playlist tasks running
Previously, stopping a playlist only set flags but didn't cancel the
actual asyncio task. When starting a new playlist immediately after:
1. Old task continues running (just between patterns)
2. New playlist clears stop_requested
3. Old task sees stop_requested=False and continues!
Now:
- Track the playlist task in _current_playlist_task
- cancel_current_playlist() properly cancels and awaits the task
- stop_actions() calls cancel_current_playlist() when clearing playlist
- run_playlist() cancels any existing task before starting new one
This prevents "ghost" playlists from running in parallel.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add validation for coordinate calculations to prevent GRBL error:2
GRBL error:2 means "bad number format" - happens when coordinates
contain inf, nan, or other invalid values.
Added:
- Protection against division by zero in offset calculation
- Validation that coordinates are finite before sending to GRBL
- Detailed error logging when invalid values detected
- Stops pattern gracefully instead of hanging on repeated errors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Stop pattern on GRBL error/alarm instead of hanging
When GRBL returns an error (like error:2) or alarm, it will NOT send
'ok' afterwards. Previously the code just logged a warning and kept
waiting for 'ok' until timeout (120s).
Now GRBL errors/alarms immediately:
- Log the error with the failed command
- Set stop_requested = True
- Return False to stop pattern execution
This provides immediate feedback instead of hanging for 2 minutes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add NaN/Infinity validation before sending GRBL coordinates
GRBL error:2 ("Numeric value format is not valid") can be caused by
NaN or Infinity values in coordinates. This can happen if:
- y_steps_per_mm = 0 (division by zero in offset calculation)
- gear_ratio = 0 (division by zero in offset calculation)
Now validates coordinates before sending and logs detailed diagnostic
info if invalid values are detected, helping identify the root cause.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix homing timeout due to WPos vs MPos format mismatch
- Support both WPos and MPos GRBL status formats (depends on $10 setting)
- Add guard against None machine position when homing fails
- Prevents NoneType + float crash in pattern execution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix get_machine_position to also accept WPos format
Missed this function in the previous fix - it was still checking
only for MPos, causing position query timeouts after homing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add debug logging for GRBL response timeout investigation
- Remove time.sleep(0.01) to match main branch behavior
- Log unexpected GRBL responses (neither ok/error/alarm)
- Log waiting status every 30s during long waits
This helps diagnose why Pi 3B+ times out on feature branch but not main.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* TEMP: Disable real-time scheduling to test Pi 3B+ serial timeout
Hypothesis: SCHED_RR priority 60 + CPU 0 pinning may be blocking
serial interrupt handling on Pi 3B+, causing the motion thread
to never receive 'ok' responses from GRBL.
CPU 0 typically handles hardware interrupts including serial ports.
Running a real-time thread there might starve the interrupt handler.
This is a test commit - will be reverted or made configurable
based on results.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix serial echo causing GRBL communication failure on Pi 3B+
Root cause: On Pi 3B+ with /dev/ttyAMA0, the serial port had echo
enabled at the TTY level. This caused:
1. Sent G-code commands echoed back as "responses"
2. Motion thread waiting for 'ok' but receiving its own commands
3. Buffer corruption merging commands (G1 G53 -> G10G53)
4. GRBL error from corrupted command, stopping pattern
Fix: Disable ECHO and related flags via termios when opening the
serial connection. This ensures raw serial communication without
any TTY processing that could interfere.
Also re-enables real-time scheduling which wasn't the cause.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Handle FluidNC command echo gracefully
FluidNC firmware echoes commands back before sending 'ok'.
Instead of logging warnings for each echo, silently skip echoed
G-code commands (G0, G1, G2, G3, $J, M) and continue reading
to get the actual 'ok' response.
This reduces log spam while maintaining proper error detection
for actual GRBL errors and alarms.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Disable SCHED_RR - causes serial buffer corruption on Pi 3B+
The real-time scheduling (SCHED_RR priority 60 + CPU 0 pinning)
appears to cause serial buffer corruption on Pi 3B+, resulting in:
- Commands being merged (e.g., "G1 G53" -> "G10G53")
- GRBL errors from corrupted commands
- Pattern execution failures
This is separate from the FluidNC echo issue (which main branch
handled silently). The corruption is timing-related and specific
to certain Pi models.
Keeping disabled until we can investigate proper CPU affinity
settings that don't interfere with serial I/O.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert termios modifications - may cause serial corruption
The termios changes (disabling ECHO, ICANON) may be causing
serial data corruption (G1 -> O1). Main branch doesn't have
these modifications and works fine.
The FluidNC echo is handled in pattern_manager.py by ignoring
echoed G-code commands, so termios-level echo suppression
is not needed.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add serial corruption retry logic for Pi 3B+ UART timing issues
- Add reset_input_buffer() method to SerialConnection for clearing stale data
- Clear serial input buffer before sending G-code commands
- Add 5ms post-send delay for UART buffer stabilization
- Implement retry logic (up to 3 retries) for corruption-type GRBL errors
(error:1, error:2, error:20-23 indicate syntax errors from bit flips)
- Throttle WebSocket status polling during pattern execution (2s vs 1s)
- Detect MSG:ERR: Bad GCode messages as corruption indicators
This addresses serial corruption on Pi 3B+ where G-code commands get
garbled (e.g., G53 → G5s) due to UART timing issues under asyncio load.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add timeout recovery logic for lost 'ok' responses on Pi 3B+
When a 120s timeout occurs waiting for 'ok', the code now attempts recovery:
- Sends status query '?' to check if machine is still responsive
- If machine is Idle: assumes command completed, 'ok' was lost - continues pattern
- If machine is Run: extends timeout as movement is still in progress
- If delayed 'ok' received during recovery: accepts it and continues
- If no response: retries the command (up to 2 times) before stopping
This handles cases where the serial 'ok' response is completely lost
due to UART timing issues on Pi 3B+, without unnecessarily stopping patterns.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add comprehensive logging to timeout recovery for diagnosis
Enhances the timeout recovery section with detailed logging to help
diagnose why recovery fails in some cases (user reported GRBL was
Idle but recovery didn't detect it). New logging includes:
- Failed G-code command and retry counts
- Connection type and state validation
- Buffer clear confirmation and bytes waiting
- Each response received during recovery (at INFO level)
- Summary of all responses received
- Clear failure summary with possible causes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Switch from machine coordinates (G53) to work coordinates with reset
This prevents MPos from accumulating indefinitely and hitting soft limits
(error:30) during long pattern sequences.
Changes:
- Add reset_work_coordinates() to clear G92/G54 offsets on connection
- Remove G53 from G1 commands to use work coordinates (G54 default)
- Add G92 X0 in reset_theta() to reset work X position each pattern
- Reduce coordinate precision to 2 decimals
The G92 X0 command sets current position as X=0 without moving, keeping
coordinates bounded. Soft limits still check MPos, so safety is preserved.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Increase serial retry limits to 10 for better Pi 3B+ reliability
Raises both corruption and timeout retry limits from 3/2 to 10 to handle
persistent UART timing issues on Raspberry Pi 3B+.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add Y axis reset to work coordinate reset function
Now resets both X and Y work coordinates with G92 X0 Y0 to prevent
coordinate accumulation on both axes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Clean up log messages
- Simplify work coordinates reset log message
- Remove noisy real-time scheduling disabled log
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Revert process isolation and real-time scheduling (commit 5a8643c)
Remove ProcessPoolExecutor and real-time scheduling features that were
causing issues on some systems:
- Remove modules/core/process_pool.py
- Remove modules/core/scheduling.py
- Revert cache_manager.py to use asyncio.to_thread
- Revert preview.py to use asyncio.to_thread
- Remove CPU pinning and priority elevation from motion/LED threads
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add .vite/ to gitignore
Vite's cache directory should not be committed.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use $Bye for hard reset instead of G92 X0 Y0
G92 only sets work coordinate offset without changing MPos, causing
position queries to return stale values. $Bye performs a full FluidNC
soft reset which properly clears position counters to 0.
- Add perform_soft_reset() in connection_manager with proper wait logic
- Wait for "Grbl" startup banner (5s timeout) before proceeding
- Send $X unlock after reset in case of alarm state
- Simplify reset_theta() to use shared soft reset function
- Update /soft_reset endpoint to use new function
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add soft reset on device init and support both GRBL/FluidNC
- Perform soft reset before get_machine_steps() in device_init()
- Detect firmware type and use appropriate reset command:
- FluidNC: $Bye
- GRBL: Ctrl+X (0x18)
- Refactor to sync/async versions for different contexts
- Ensures controller is in known state before querying
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix $Bye reset reliability and prevent position drift
- Add idle check before reset_theta() to prevent error:25 when
controller is still processing commands
- Add retry logic (3 attempts with 5s/7s/9s timeouts) to soft reset
- Fail-fast: only set position to (0,0) when confirmation received,
preventing position drift from accumulating over long operations
- Add CPU affinity: pin Docker backend to cores 0-2, touch app to
core 3 with Nice=10 to prevent serial I/O timing issues
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix device init: check position before reset to avoid unnecessary homing
Query machine position BEFORE soft reset to determine if homing is needed.
Previously, reset was done first which zeroed position, causing the
comparison to always trigger homing even when machine retained position.
New sequence:
1. Query machine position (before reset)
2. Compare with saved state to decide if homing needed
3. Perform soft reset for clean controller state
4. Home only if position was mismatched
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Increase soft reset retries to 5 with exponential backoff
- Increase max_retries from 3 to 5 for better reliability
- Timeout uses 1.5x backoff: 5s, 7.5s, 11s, 17s, 25s
- Retry delay uses 2x backoff: 1s, 2s, 4s, 8s between attempts
- Gives controller more time to recover from transient issues
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Increase homing timeout from 90s to 120s
Provides buffer for controller to stabilize after soft reset
recovery before expecting reliable communication during homing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update frontend: remove Ctrl+X reference from soft reset UI
Backend now auto-detects firmware and sends appropriate command:
- FluidNC: $Bye
- GRBL: Ctrl+X (0x18)
UI now shows generic "Soft Reset" instead of "Ctrl+X Soft Reset"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* extend logs
* chore(backend-testing-01): create test directory structure
- tests/ root with __init__.py
- tests/unit/ for mocked unit tests (CI)
- tests/integration/ for hardware tests (local only)
- tests/fixtures/ for test data files
* chore(backend-testing-01): add testing dependencies
- pytest + pytest-asyncio for async test support
- pytest-cov for coverage reporting
- pytest-timeout for test timeouts
- httpx for async API testing
- mock-serial for serial port mocking
- factory-boy + faker for test data generation
* chore(backend-testing-01): configure pytest in pyproject.toml
- testpaths set to tests/
- asyncio_mode = auto for pytest-asyncio
- asyncio_default_fixture_loop_scope = function
- markers for hardware and slow tests
- coverage settings with relative_files = true
* chore(backend-testing-01): create root conftest.py with shared fixtures
- pytest_configure for CI detection and markers
- pytest_collection_modifyitems to auto-skip hardware tests in CI
- async_client fixture for API testing with httpx
- mock_state fixture with comprehensive state defaults
- mock_connection fixture for serial/websocket mocking
- patterns_dir fixture for temporary test patterns
* test(backend-testing-01): add playlist_manager CRUD tests
- test_load_playlists_empty_file, test_load_playlists_with_data
- test_create_playlist, test_create_playlist_overwrites_existing
- test_get_playlist_exists, test_get_playlist_not_found
- test_modify_playlist, test_delete_playlist
- test_list_all_playlists, test_add_to_playlist
- test_rename_playlist_* (6 edge cases)
20 tests total, all passing
* test(backend-testing-01): add pattern_manager parsing tests
- parse_theta_rho_file tests (valid, comments, empty lines, not found)
- parse_theta_rho_file edge cases (invalid lines, whitespace, scientific notation, negative)
- list_theta_rho_files tests (basic, subdirectories, skip cached_images)
- get_status tests (idle, running, paused, playlist)
- is_clear_pattern tests (standard, mini, pro, regular patterns)
22 tests total, all passing
* test(backend-testing-01): add connection_manager parsing tests
- parse_machine_position tests (MPos, WPos formats)
- parse_machine_position edge cases (invalid, run/alarm state, negative, high precision)
- list_serial_ports tests (filter ignored, empty, all ignored)
- BaseConnection interface tests
- IGNORE_PORTS and DEPRIORITIZED_PORTS constant tests
- is_machine_idle tests (no connection, idle, running)
21 tests total, all passing
* chore(backend-testing-01): create unit conftest with mocked dependencies
- mock_state_unit fixture with comprehensive state defaults
- mock_connection_unit fixture for hardware mocking
- app_with_mocked_state fixture for patched app testing
- async_client_with_mocked_state fixture for API testing
- cleanup_app_overrides fixture for dependency cleanup
* test(backend-testing-01): add status and info API endpoint tests
- /serial_status tests (connected, disconnected states)
- /list_serial_ports tests (returns list, empty)
- /api/settings tests (structure, effective_table_type)
- /api/table-info tests (configured, not configured)
9 tests total, all passing
* test(backend-testing-01): add pattern API endpoint tests
- /list_theta_rho_files tests (list, empty)
- /list_theta_rho_files_with_metadata tests (structure)
- /get_theta_rho_coordinates tests (valid file, not found)
- /run_theta_rho tests (disconnected, during homing, not found)
- /stop_execution tests (success, disconnected)
- /pause_execution and /resume_execution tests
- /delete_theta_rho_file tests (success, not found)
14 tests total, all passing
* test(backend-testing-01): add playlist API endpoint tests
- /list_all_playlists tests (list, empty)
- /get_playlist tests (exists, auto-creates if not found)
- /create_playlist and /modify_playlist tests
- /delete_playlist tests (success, not found)
- /rename_playlist tests (success, not found)
- /add_to_playlist tests (success, not found)
- /run_playlist tests (disconnected, during homing)
- /skip_pattern tests (success, no playlist)
16 tests total, all passing
* ci(backend-testing-01): add GitHub Actions workflow for tests
- Triggers on push to main/feature branches and PRs
- Runs unit tests with CI=true (skips hardware tests)
- Generates coverage report with pytest-cov
- Uploads coverage to Codecov
- Includes Ruff linting job (continue-on-error)
- Uses Python 3.11 with pip caching
* test(backend-testing-01): add integration test skeleton for hardware
- Add conftest.py with hardware detection fixtures
- Add test_hardware.py with @pytest.mark.hardware tests
- Tests auto-skip without --run-hardware flag
- Tests auto-skip in CI (when CI=true)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test(integration): expand hardware tests with movement and pattern execution
Fixes:
- Status query: Don't assume Idle state, accept any valid GRBL status
- Soft reset: Auto-detect firmware (FluidNC=$Bye, GRBL=Ctrl+X)
New tests:
- test_firmware_detection: Detect FluidNC vs GRBL firmware
- test_homing_sequence: Full homing cycle with position verification
- test_move_to_perimeter: Move ball to rho=1.0
- test_move_to_center: Move ball to rho=0.0
- test_execute_star_pattern: Run star.thr pattern end-to-end
- test_websocket_status_endpoint: Test /ws/status WebSocket
- test_position_saved_on_disconnect: Verify state persistence
Total: 13 integration tests (all skip without --run-hardware)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test(integration): add playback controls and playlist tests
Playback controls (test_playback_controls.py):
- TestPauseResume: pause_during_pattern, resume_after_pause
- TestStop: stop_during_pattern, force_stop, pause_then_stop
- TestSpeedControl: set_speed_during_playback, speed_bounds, change_speed_while_paused
- TestSkip: skip_pattern_in_playlist, skip_while_paused
Playlist tests (test_playlist.py):
- TestPlaylistModes: single_mode, loop_mode, shuffle
- TestPlaylistPause: pause_between_patterns, stop_during_playlist_pause
- TestPlaylistClearPattern: playlist_with_clear_pattern
- TestPlaylistStateUpdates: current_file_updates, playlist_index_updates, progress_updates
- TestWebSocketStatus: status_updates_during_playback
Total: 33 integration tests (all skip without --run-hardware)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: add testing guide README
Includes:
- Quick start commands
- Test structure overview
- Unit vs integration test instructions
- Coverage report generation
- CI behavior explanation
- Examples for adding new tests
- Troubleshooting section
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(test): account for angular_homing_offset_degrees in homing test
The homing sequence sets theta to the configured offset (e.g., 135°),
not 0. Test now compares against state.angular_homing_offset_degrees.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(test): wait for idle after move instead of sleep
Movement tests now use check_idle_async() to properly wait for the
machine to complete the move, instead of unreliable time.sleep(2).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: move_to_center and move_to_perimeter wait for idle before returning
API endpoints now call check_idle_async(timeout=60) after move_polar
to ensure the machine has completed the movement before returning success.
This gives the frontend accurate feedback that the move is complete.
Tests updated to use the API endpoints directly instead of calling
move_polar + manual idle check.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(test): remove unsupported timeout arg from receive_json()
FastAPI TestClient's WebSocket receive_json() doesn't accept timeout.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(test): fix WebSocket status format and add fast test speed
- WebSocket returns {'type': 'status_update', 'data': {...}}
Tests now check message.get("data") for status fields
- Add autouse fixture to set speed=500 for faster integration tests
- Restores original speed after each test
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: preserve playlist state when async task is cancelled by TestClient
Root cause: FastAPI TestClient cancels async background tasks when HTTP
requests complete, causing playlist state to be cleared before tests
could verify it.
Changes:
- playlist_manager: Set all state vars (playlist_mode, current_playlist_index,
current_playing_file) BEFORE creating async task for immediate visibility
- pattern_manager: Handle CancelledError specially to preserve state when
task is externally cancelled (vs normal completion or user stop)
- pattern_manager: Only clear current_playing_file in stop_actions() when
clear_playlist=True, since caller sets it immediately after otherwise
- pattern_manager: Fix Python 3.9 compatibility (asyncio.timeout -> wait_for)
- main.py: Skip endpoint proactively advances state when task not running
- main.py: Fix HTTPException being incorrectly wrapped in 500 error
- tests: Fix pattern path check (./patterns/ prefix was missing)
- tests: Add reset_asyncio_events fixture to handle event loop isolation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: stop_execution and send_coordinate wait for idle before returning
- stop_actions() now waits for hardware to reach idle state (30s timeout)
- send_coordinate endpoint waits for idle after move (60s timeout)
- Clear stop_requested flag before idle check to allow check_idle_async to work
This ensures API responses only return after the machine has physically
stopped moving, consistent with move_to_center and move_to_perimeter.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore(01-01): install Vitest and React Testing Library
- Add vitest, @vitest/ui, jsdom as dev dependencies
- Add @testing-library/react, @testing-library/jest-dom, @testing-library/user-event
- Create vitest.config.ts with jsdom environment, globals, and coverage
- Create src/test/setup.ts with jest-dom matchers and cleanup
- Add test scripts: test, test:watch, test:ui, test:coverage
* chore(01-01): install and configure Playwright
- Add @playwright/test as dev dependency
- Install Chromium browser for E2E testing
- Create playwright.config.ts with chromium project, webServer, and CI settings
- Add test:e2e and test:e2e:ui scripts
* chore(01-01): configure MSW for API mocking
- Add msw as dev dependency for mocking API requests
- Create src/test/mocks/handlers.ts with mock endpoints for patterns, playlists, status
- Create src/test/mocks/server.ts with MSW server setup
- Update src/test/setup.ts to start/stop MSW server and reset handlers between tests
* test(01-01): add sample tests and TypeScript types
- Create src/__tests__/sample.test.tsx to verify Vitest + RTL + jest-dom work
- Create e2e/sample.spec.ts to verify Playwright configuration
- Add vitest/globals to TypeScript types for test globals (describe, it, expect)
* chore(01-01): add @vitest/coverage-v8 for coverage reports
- Add @vitest/coverage-v8@3.2.4 to match vitest version
- npm run test:coverage now generates v8 coverage reports
* chore(01-01): ignore frontend test artifacts
- Add frontend/coverage/ for Vitest coverage reports
- Add frontend/playwright-report/ for Playwright HTML reports
- Add frontend/test-results/ for Playwright test results
* docs(01): complete test-infrastructure phase
- Vitest configured with jsdom, RTL, coverage (INF-1)
- Playwright installed with Chrome configuration (INF-2)
- MSW configured for API mocking (INF-3)
- Directory structure established (INF-4)
All 4 must-haves verified. 2 component tests + 1 E2E test passing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(02-01): add test utilities and expanded MSW handlers
- Create renderWithProviders helper with BrowserRouter wrapper
- Add mock data generators for patterns, playlists, and status
- Expand MSW handlers for all component endpoints (50+ handlers)
- Add pattern, playlist, playback, and table control endpoints
- Create MockWebSocket class for WebSocket testing
- Add browser API mocks (IntersectionObserver, ResizeObserver, canvas)
- Update test setup with browser mocks and data reset
* test(02-01): add component tests for critical pages
BrowsePage tests:
- Pattern listing renders from API
- Search filters patterns by name
- Empty state and error handling
- Pattern selection opens sheet
PlaylistsPage tests:
- Playlist listing and selection
- Create/edit/delete buttons present
- Run playlist triggers API
- Pattern count display
TableControlPage tests:
- Renders all control sections (Home/Stop/Reset/Speed)
- Movement controls call correct APIs
- Speed input submits on Enter and button click
- Dialog trigger has correct aria attributes
NowPlayingBar tests:
- Visibility prop handling
- Props acceptance without crashing
- onClose callback handling
Test utilities:
- Updated setup.ts for WebSocket mocking
- Changed onUnhandledRequest to 'warn' for WebSocket compatibility
* docs(02-01): complete component-tests phase
Tasks completed: 6/6
- Test utilities module with renderWithProviders
- Expanded MSW handlers (50+ endpoints)
- WebSocket mock utility
- Browser API mocks (IntersectionObserver, canvas, localStorage)
- Component tests for BrowsePage, PlaylistsPage, TableControlPage, NowPlayingBar
- Test verification with 42 passing tests
SUMMARY: .planning/phases/02-component-tests/02-01-SUMMARY.md
* chore(03-01): add integration test infrastructure
- Add apiCallLog tracking for verifying API call sequences
- Add resetApiCallLog() to reset between tests
- Add logApiCall() to key handlers (run_theta_rho, playlists, playback)
- Update test setup to reset API call log in beforeEach
- Add renderApp() helper for full app integration tests
* test(03-01): add pattern flow integration tests
- Test browse -> select -> run pattern flow
- Verify API call (run_theta_rho) with correct file parameter
- Test search -> filter -> run filtered pattern
- Verify mock state updates after pattern starts
- Verify API call logging with timestamp and method
* test(03-01): add playlist flow integration tests
- Test view and select playlist flow
- Verify run_playlist API call with correct playlist_name
- Test queue population from playlist files
- Test create playlist via dialog
- Test delete playlist with confirmation
- Fix preview_thr_batch handler to accept file_names parameter
* test(03-01): add playback flow integration tests
- Test pattern playback lifecycle (start/stop)
- Test playlist playback with queue population
- Test stop_execution API resets all playback state
- Verify state transitions (idle -> running -> stopped)
- Verify API call sequences for pattern and playlist runs
* docs(03-01): complete integration tests phase
Tasks completed: 5/5
- Integration test infrastructure (apiCallLog, renderApp)
- Pattern flow tests (6 tests)
- Playlist flow tests (8 tests)
- Playback flow tests (8 tests)
SUMMARY: .planning/phases/03-integration-tests/03-01-SUMMARY.md
* test(04-01): add Playwright API mock utilities
- Add mock data for patterns, playlists, and status
- Implement setupApiMocks() for route interception
- Add getMockStatus/setMockStatus for state verification
- Support pattern, playlist, and playback control endpoints
* test(04-01): add pattern flow E2E tests
- Test pattern list display on browse page
- Test pattern selection opens detail panel
- Test pattern execution triggers running state
- Test search filtering functionality
* test(04-01): add playlist flow E2E tests
- Test playlist list display
- Test playlist selection and execution
- Test navigation between browse and playlists pages
* test(04-01): add table control E2E tests
- Test control page displays buttons
- Test home action can be triggered
- Test navigation bar shows all page links
* test(04-01): update sample spec with infrastructure tests
- Test app loads and renders header
- Test bottom navigation is visible
- Test dark mode toggle presence
* ci(04-01): extend workflow for frontend tests
- Add frontend-test job for Vitest tests
- Add frontend-e2e job for Playwright tests
- Update paths trigger to include frontend files
- Configure Node.js 20 with npm caching
- Install Playwright browsers in CI
- Upload Playwright report on failure
* fix(04-01): fix E2E tests and add WebSocket mocking
- Add WebSocket mocking for /ws/status, /ws/logs, /ws/cache-progress
- Fix Playwright config to use port 5174 to avoid conflicts
- Add static file and additional API endpoint mocks
- Fix pattern-flow test to use exact button name
- Fix playlist-flow test to use title selector for Run button
* docs(04-01): complete E2E and CI phase
Tasks completed: 7/7
- Playwright API mock utilities with WebSocket support
- Pattern flow E2E tests (4 tests)
- Playlist flow E2E tests (3 tests)
- Table control E2E tests (3 tests)
- Updated sample spec (3 tests)
- Extended CI workflow for frontend tests
- Fixed E2E tests and WebSocket mocking
SUMMARY: .planning/phases/04-e2e-ci/04-01-SUMMARY.md
* add gitignore
* feat: add sensor homing failure recovery popup
When sensor homing fails during startup or manual homing:
- Show a blocking popup notifying user to check sensor position
- Provide "Retry Sensor Homing" option
- Provide "Switch to Crash Homing" option
- Connection is not established until user takes action
- Add /recover_sensor_homing API endpoint for recovery
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: resolve TypeScript errors in test files
- Remove unused 'screen' import from NowPlayingBar.test.tsx
- Add missing IntersectionObserver properties to MockIntersectionObserver
- Use type-only imports for PatternMetadata, PreviewData in handlers.ts
- Use type-only imports for RenderOptions, ReactElement, ReactNode in utils.tsx
- Export IntegrationWrapper to resolve unused variable warning
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: add GRBL Hold and Alarm state recovery during pattern execution
When the GRBL controller enters Hold state (e.g., from serial byte corruption
triggering '!' command) or Alarm state, the timeout recovery logic now properly
handles these states instead of failing after multiple retries.
- Hold state: sends '~' (cycle start) to resume motion
- Alarm state: sends '$X' (unlock) to clear fault condition
- Both states now detected as valid status responses during recovery
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: define v3 requirements for touch app CPU optimization
16 requirements across 5 categories:
- QML logging cleanup (6)
- Timer optimization (2)
- WebSocket optimization (2)
- Preview cache (2)
- Verification (4)
Target: <20% idle CPU on Pi 3B+, Pi 4, Pi 5
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: create v3 roadmap with single-phase CPU optimization
Phase 1: CPU Optimization
- 16 requirements covered (100%)
- QML logging cleanup, timer optimization, WebSocket, preview cache
- Target: <20% idle CPU on Pi 3B+, Pi 4, Pi 5
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs(01): create CPU optimization execution plan
6 tasks covering 16 requirements:
- Remove QML console.log spam (5 tasks)
- Optimize backend timer, WebSocket, preview cache (1 task)
Target: <20% idle CPU on Pi 3B+, Pi 4, Pi 5
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* perf(01-01): remove MouseArea console.log spam in main.qml
- Remove console.log from onPressed handler
- Remove console.log from onPositionChanged handler
- Remove console.log from onClicked handler
- Keep backend.resetActivityTimer() calls for screen timeout
* perf(01-01): remove remaining debug logs from main.qml
- Remove console.log from onCurrentPageIndexChanged
- Remove console.log from onShouldNavigateToExecutionChanged
- Remove console.log from onExecutionStarted
- Remove console.log from onScreenStateChanged
- Remove console.log from onBackendConnectionChanged
- Remove console.log from onRetryConnection
- Remove console.log from StackLayout.onCompleted
- Remove console.log from onTabClicked
* perf(01-01): remove debug logs from ConnectionStatus.qml
- Remove all console.log statements from color binding
- Remove onSerialConnectionChanged debug handler
- Remove Component.onCompleted debug handler
- Remove onBackendChanged debug handler
- Simplify color binding to single-line expression
* perf(01-01): remove debug logs from ExecutionPage.qml
- Remove onBackendChanged debug handler
- Remove Component.onCompleted debug handler
- Remove onSerialConnectionChanged debug handler
- Remove onConnectionChanged debug handler
- Remove console.log from onExecutionStarted
- Remove console.log from Image source binding
- Remove onStatusChanged debug handler
- Remove onSourceChanged debug handler
- Simplify Image source binding
* perf(01-01): remove debug logs from remaining QML files
TableControlPage.qml:
- Remove console.log from signal handlers
ModernPlaylistPage.qml:
- Remove console.log from onSelectedPlaylistChanged
- Remove Component.onCompleted debug handler
- Remove console.log from playlist execution
- Remove console.log from shuffle toggle
- Remove console.log from run mode and clear pattern handlers
ThemeManager.qml:
- Remove console.log from onDarkModeChanged
BottomNavTab.qml:
- Remove console.log from icon mapping
* perf(01-01): optimize timer, WebSocket, and preview cache in backend.py
Timer optimization (REQ-TIMER-01, REQ-TIMER-02):
- Replace 1-second continuous polling with event-driven single-shot timer
- Timer now fires once after timeout duration, not every second
- _reset_activity_timer restarts timer with full timeout
- Renamed _check_screen_timeout to _screen_timeout_triggered
WebSocket optimization (REQ-WS-01, REQ-WS-02):
- Remove print statements from pattern change detection
- Remove print statements from pause state changes
- Remove print statements from serial connection changes
- Remove print statements from speed changes
- Only emit signals when values actually change (existing behavior)
Preview cache (REQ-CACHE-01, REQ-CACHE-02):
- Add _preview_cache dictionary in __init__
- Check cache before filesystem lookup in _find_pattern_preview
- Cache positive results after successful lookup
- Add _clear_preview_cache method for cache invalidation
* docs(01): complete CPU optimization phase
Phase 1 verified: 6/6 must-haves passed (code inspection)
Code changes complete:
- Removed 50+ console.log statements from QML files
- Event-driven screen timeout (single-shot timer)
- WebSocket signal optimization
- Pattern preview path caching
Pending: Hardware verification on Pi (5 requirements)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: stop tracking .planning directory
Files were tracked before .gitignore entry was added.
Removing from index only - files remain locally.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): eliminate CPU busy-wait in touch monitor thread
The touch monitor thread was using a 10ms polling loop (100 iterations/sec)
while waiting for touch input, causing 100% CPU usage when screen was off.
Fixed by:
- Using select() with 1-second timeout instead of busy polling
- Removed time.sleep() calls that created artificial delays
- Changed evtest subprocess to binary mode for select() compatibility
This should reduce idle CPU from 100%+ to under 10% when screen is off.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(led): turn off LEDs during Still Sands when table goes idle
Extended start_idle_led_timeout() to handle Still Sands LED control:
- Now checks if in scheduled pause period with LED control enabled
- Turns off LEDs instead of showing idle effect during Still Sands
- Added check_still_sands parameter for cases where caller handles logic
This fixes LEDs not turning off when table goes idle during Still Sands
period (pattern stops, skips, completes, or playlist ends).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): resolve 100% CPU usage with qasync event loop pattern
Root cause: The QEventLoop + run_forever() pattern in qasync causes
CPU spinning. Combined with hoverEnabled MouseArea generating continuous
events on capacitive touchscreens.
Changes:
- main.py: Use qasync.run() with async_main() pattern instead of
QEventLoop.run_forever() - this properly yields to Qt event loop
- main.qml: Remove hoverEnabled and onPositionChanged from MouseArea
to prevent continuous event generation on touch displays
- backend.py: Add 100ms throttling to _reset_activity_timer() as
defense-in-depth against any remaining event storms
- requirements.txt: Bump qasync to >=0.28.0 for latest fixes
This supersedes the previous fix in c2b1851 which addressed a symptom
(touch monitor thread) rather than the root cause (event loop pattern).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(dw): update touch app dependencies during dw update
The previous touch app update logic did a redundant git pull (code is
already part of main repo), but didn't update pip dependencies. This
caused issues when requirements.txt changed (e.g., qasync version bump).
Now properly:
- Updates pip dependencies from requirements.txt
- Restarts the touch app service to apply changes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): use proper qasync event wait pattern
The previous fix used a polling loop with app.processEvents() which
caused RuntimeError when aiohttp tasks tried to run concurrently.
The issue: manually calling processEvents() inside an async function
creates reentrant async execution, conflicting with other tasks.
Fix: Use asyncio.Event with app.aboutToQuit signal instead of polling.
qasync handles Qt/asyncio integration internally - we just await the
quit signal without manual event processing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): add proper logging to touch monitoring
- Add logger to backend.py (was using print() which journald doesn't capture)
- Add logging to _screen_timeout_triggered, _start_touch_monitoring
- Simplify _monitor_touch_input with proper select() non-blocking I/O
- Remove unused touch-monitor script code path
- Use binary mode for evtest subprocess to work with select()
This will help diagnose touch-to-wake issues and ensure the flow is
visible in systemd journal logs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): restore QEventLoop pattern for proper QTimer support
The qasync.run() + await quit_event.wait() pattern doesn't properly
process QTimer.singleShot callbacks, which broke touch-to-wake
(the callback to start touch monitoring never fired).
Restored the original QEventLoop + run_forever() pattern which
properly integrates Qt timers. Using qasync 0.28.0 which should
have fixes for the CPU spin issues reported in earlier versions.
This should fix touch-to-wake while maintaining reasonable CPU usage.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* revert(touch): revert CPU optimization attempts - none resolved 100% CPU
Reverting to e5265ac state. The following approaches were tried but
did not fix the 100% CPU usage issue:
## Approach 1: Remove QML console.log statements
- Removed 50+ console.log from main.qml, ExecutionPage.qml,
ConnectionStatus.qml, ModernPlaylistPage.qml, TableControlPage.qml,
ThemeManager.qml, BottomNavTab.qml
- Result: Did not fix CPU issue
## Approach 2: Remove MouseArea hover events
- Removed hoverEnabled and onPositionChanged from main.qml MouseArea
- Theory: Capacitive touchscreens generate continuous hover events
- Result: Did not fix CPU issue
## Approach 3: Event-driven screen timeout timer
- Changed from 1-second polling loop to single-shot QTimer
- Timer fires once after timeout instead of every second
- Result: Did not fix CPU issue
## Approach 4: Touch monitor thread select() optimization
- Changed from 10ms busy-polling to select() with 1-second timeout
- Used binary mode for evtest subprocess
- Result: Did not fix CPU issue
## Approach 5: qasync event loop pattern changes
- Tried qasync.run() + async_main() instead of QEventLoop.run_forever()
- Bumped qasync to >=0.28.0
- Result: Broke touch-to-wake (QTimer callbacks didn't fire)
## Approach 6: asyncio.Event wait pattern
- Used asyncio.Event with app.aboutToQuit signal
- Avoided manual processEvents() to prevent reentrant async execution
- Result: Did not fix CPU issue
## Approach 7: Activity timer throttling
- Added 100ms throttle to _reset_activity_timer()
- Result: Did not fix CPU issue
## Approach 8: Restored QEventLoop pattern
- Restored QEventLoop + run_forever() for proper QTimer support
- Result: Fixed touch-to-wake but CPU issue remained
Root cause still unknown. Need fresh investigation approach.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): resolve 100% CPU on screen timeout with proper thread cleanup
Root cause: Touch monitoring thread had blocking readline() that couldn't
exit when screen woke up, plus missing cleanup in _turn_screen_on().
Changes:
- Add threading.Event stop flag for clean thread signaling
- Use select() with 0.5s timeout for non-blocking reads
- Add thread cleanup in _turn_screen_on() with join timeout
- Add finally block for guaranteed subprocess termination
- Check stop flag during all delays and loops
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): add CPUQuota limit and fix evtest binary mode buffering
- Add CPUQuota=25% to systemd service as hard limit safety net
- Switch evtest to binary mode to fix select() buffering issues
- Skip evtest initialization output before monitoring for events
- Prevents tight loop during evtest startup device info dump
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test(touch): disable touch monitoring to verify 100% CPU hypothesis
Temporarily disable touch monitoring on screen timeout to confirm
that evtest subprocess is the root cause of 100% CPU usage.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(touch): add full-page pattern selector and enhance playlist management
Replace the pattern selector popup with a full-page navigation that mirrors
the Browse page layout. Patterns already in the playlist are highlighted with
a blue border and checkmark badge for easy identification.
Key changes:
- Add PatternSelectorPage.qml with grid layout and instant visual feedback
- Update ModernPlaylistPage to navigate to selector instead of popup
- Add playlist creation/deletion buttons and pattern removal functionality
- Enhance search with Enter-to-search pattern across pages
- Add raw pattern tracking for proper API calls
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: remove dead /get_speed proxy and add missing /upload_theta_rho proxy
- Remove /get_speed proxy entry that was never used by frontend or backend
- Add /upload_theta_rho proxy for pattern file uploads
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ui): replace Material Icons delete button with Lucide SVG icon
Material Icons font wasn't loading on Raspberry Pi. Lucide React icons
are SVG-based and work reliably across all devices.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): improve text visibility in dark mode
- Fix placeholderText color in ThemeManager (#606060 -> #9a9a9a)
The old color was nearly invisible on dark backgrounds
- Add placeholderTextColor to all search TextField components
- Add proper text colors to PatternListPage search field
- Add Components import alias to PatternListPage for ThemeManager access
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): improve dark mode visibility for buttons and text
- Style search close (✕) buttons with proper themed colors
- Add contentItem with ThemeManager colors for visibility
- Add hover/press states using buttonBackgroundHover
- Fix PlaylistPage.qml with full theme support:
- Background colors, borders, text colors
- Back button with proper contentItem
- Fix PatternListPage "No patterns found" label color
All flat buttons and text elements now use ThemeManager colors
for proper visibility in both light and dark modes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ui): replace all Material Icons with Lucide SVG icons in TableControlPage
Material Icons font doesn't load reliably on Raspberry Pi.
Lucide React icons are bundled SVGs that work everywhere.
Replaced ~30 icons including:
- Loading spinners (sync → Loader2)
- Navigation (home, back arrows)
- Actions (power, restart, delete, send)
- Status indicators (check, warning)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ui): replace all Material Icons with Lucide SVG icons in PlaylistsPage
Replaced ~25 icons including:
- Navigation (add, back, close)
- Actions (play, save, edit, delete, shuffle, repeat)
- Content (playlist, folder, music, image)
- Feedback (check, search, sort arrows)
Material Icons font doesn't load reliably on Raspberry Pi.
Lucide React icons are bundled SVGs that work everywhere.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(frontend): replace all Material Icons with Lucide SVG icons in Layout
Replace bottom navigation icons and all remaining Material Icons in Layout.tsx
with Lucide React icons. This ensures icons render properly on Raspberry Pi
which has issues loading the Material Icons web font.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): fix invisible button icons in QML touch app
- Add color: "white" to ModernControlButton icon Text (was defaulting to black)
- Replace 🗑 emoji with ✕ symbol for delete button (emoji not in Pi fonts)
- Revert unnecessary frontend Lucide icon changes (3 commits)
Fixes Shutdown Pi button and Delete Playlist button visibility on Pi.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): remove unsupported power icon, make delete button red
- Remove ⏻ icon from Shutdown Pi button (not in Pi fonts)
- Change delete playlist button to always red (#dc2626)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(touch): add iconSize property to ModernControlButton
Allows independent control of icon size vs text size.
Usage: iconSize: 24 (defaults to fontSize + 2 if not set)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): increase icon sizes on TableControlPage buttons
Added iconSize: 20 to all buttons with icons for better visibility.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(touch): persist playlist settings with new defaults
Settings now saved to touch_settings.json and remembered between sessions:
- Shuffle: default ON
- Run mode: default LOOP
- Pause between patterns: default 3 HOURS
- Clear pattern: default ADAPTIVE
Backend changes:
- Added playlistShuffle, playlistRunMode, playlistClearPattern properties
- Added setPlaylist* slots to save settings
- Updated _load/_save_local_settings for playlist settings
QML changes:
- Radio buttons now reflect loaded settings (checked bindings)
- All controls call backend to persist changes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(touch): replace print statements with proper logging
- Added Python logging module with INFO level by default
- Converted 227 print statements to appropriate log levels:
- DEBUG: Verbose info (API requests/responses, file searches, screen state)
- INFO: Normal operations (connections, pattern execution, settings changes)
- WARNING: Non-critical issues (timeouts, missing resources)
- ERROR: Failures and exceptions
- Removed emojis from log messages for cleaner output
- Log format: HH:MM:SS [LEVEL] message
To enable debug logging, set: logging.getLogger("DuneWeaver").setLevel(logging.DEBUG)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(touch): remove console.log debug statements from QML
Removed 73 verbose debug logs from QML files:
- main.qml: navigation, screen state, touch events
- ConnectionStatus.qml: backend state logging
- BottomNavTab.qml: icon value logging
- ThemeManager.qml: dark mode toggle
- TableControlPage.qml: serial connection state
- ModernPlaylistPage.qml: playlist operations
- ExecutionPage.qml: image loading, execution signals
- PatternSelectorPage.qml: pattern selection
QML doesn't support log levels, so debug output is removed entirely.
For debugging, re-add console.log statements as needed.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): fix syntax error in ModernPlaylistPage.qml
Removed orphaned object literal code left behind from console.log removal.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(touch): fix shuffle toggle and improve stop reliability
- Increase stop timeout from 10s to 45s to account for backend's
idle check (10s lock + 30s idle wait)
- Fix shuffle toggle breaking QML binding by updating backend
directly instead of assigning to bound property
- Remove ~200 lines of dead Python touch monitoring code since
QML's global MouseArea handles wake-on-touch
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(ui): add draggable now playing button and optimize logo uploads
- Now Playing button can be dragged to snap to left, center, or right
- Position persists across sessions via localStorage
- Touch and mouse support with 8px drag threshold
- Increase logo upload limit from 5MB to 10MB
- Auto-optimize uploaded logos: resize to 512px max, convert to WebP
- Typical 95%+ file size reduction for large images
- SVG files pass through unchanged
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(pwa): iOS safe area handling and PWA icon transparency
- Add env(safe-area-inset-bottom) to main content bottom padding in Layout
to prevent content from being hidden behind nav bar on iPhone
- Increase base bottom padding from 5rem to 8rem to clear floating Now
Playing pill button
- Add safe area inset subtraction to viewport height calculations in
PlaylistsPage, LEDPage, and Now Playing bar expanded state
- Fix PWA icon generation to composite onto solid #0a0a0a background,
preventing iOS from showing white behind transparent custom logos
- Add maskable purpose icon entries to web manifest for better Android
adaptive icon support
- Add hard_reset_theta setting for optional machine reset on theta
normalization
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ui): add custom day selector and fix time input overlap in Still Sands
Add day-of-week toggle buttons when "Custom" is selected in the Days
dropdown, and fix time inputs overlapping on mobile by adding overflow
handling and explicit width.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(lint): resolve Ruff errors in dune-weaver-touch
- Remove unused imports: QTouchEvent, QMouseEvent, QQmlContext (F401)
- Move load_dotenv() call after all imports to fix E402
- Remove f-string prefix from strings with no placeholders (F541)
- Replace bare except with except Exception (E722)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ui): wrap MQTT buttons on mobile to prevent overflow
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(lint+test): remove unused imports, fix bare except, update delete button tests
Ruff fixes:
- Remove unused imports: Response, atexit, Tuple, Dict, Any, Union (main.py)
- Remove unused Tuple import (png_cache_manager.py)
- Remove unused Signal import (pattern_model.py, playlist_model.py)
- Replace bare except with except Exception (dune-weaver-touch/main.py)
Test fixes:
- Update playlist delete button selectors to match Trash2 lucide icon
(component switched from material-icons to lucide-react Trash2)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: add pre-commit hook for Ruff lint and frontend tests
- Runs Ruff on staged .py files, blocks commit on lint errors
- Runs vitest on staged .ts/.tsx files, blocks commit on test failures
- Gracefully skips if ruff or node_modules not available
- Auto-installs via npm prepare script
- Shareable hook in scripts/pre-commit
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Improve ETA accuracy with rho-weighted time estimation
This is the same change from PR#107, applied to the feature/react-ui branch. I have not tested this branch so please review.
The ETA calculation on the Live Pattern Preview window is erratic, often jumping around by minutes at a time +/-. This is especially noticeable when there are a lot of small moves near the center of the pattern.
This pull request replaces rate-based estimation with an algorithm that:
* Weights coordinates by rho position (center moves are slower)
* Smooths the rate using exponential moving average (alpha=0.02)
* Excludes pause time from calculations
* Requires 100 coords and 10 seconds before showing estimates
The weight formula 1/(rho+0.15) accounts for polar geometry where moves near center cover less linear distance per theta change. In my limited testing this does smooth out the ETA significantly and makes it less prone to large increases. Additional testing and validation is recommended.
This patch was written with the assistance of Claude Code
* feat(ui+led): add log search, fix PWA toast positioning, and respect LED power state on Still Sands exit
Add real-time search filtering to the log drawer (by message/logger),
use CSS env(safe-area-inset-top) for Sonner toasts instead of hardcoded
offset to fix Dynamic Island obstruction in PWA, and prevent LEDs from
auto-powering on after Still Sands when no playing/idle effect is configured
so manually-set effects persist through scheduled pause cycles.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ui): correct "Clear Sideway" typo to "Clear Sideways"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: add CONTRIBUTING.md and update README
Add a contributing guide covering dev setup, testing (unit, frontend,
e2e, hardware integration), linting, branch/commit conventions, the
Vite proxy gotcha for new API endpoints, and fork-based PR workflow.
Point contributors to the Discord #dev channel.
Update README with refreshed content, swap hero image to og-image.jpg,
and link the Contributing section to the new in-repo guide.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(nginx): add missing endpoint proxies to prevent 405 errors
add_to_queue, restart_connection, controller_restart,
recover_sensor_homing, run_theta_rho_file, download,
check_software_update, and update_software were missing from the
nginx proxy allowlist, causing POST requests to fall through to the
static file handler which returns 405 Method Not Allowed.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(ui): add update notification indicator and version link
- Add download icon with blue pulse dot in header when updates are available
- Make "Latest Version" in Settings a clickable link to GitHub releases
- Revert VERSION to 3.5.0 for pre-merge state
- Update docker-compose.yml image tags from :feature-react-ui to :main
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* update version
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: PxT <paul@droflet.net>
- Просмотр сравнение для этих 2 коммитов »
17 часов назад