diff --git a/docs/STARLARK_APPS_GUIDE.md b/docs/STARLARK_APPS_GUIDE.md index 59c79c36..d4a85a71 100644 --- a/docs/STARLARK_APPS_GUIDE.md +++ b/docs/STARLARK_APPS_GUIDE.md @@ -27,7 +27,7 @@ The Starlark Apps plugin for LEDMatrix enables you to run **Tidbyt/Tronbyte comm ### Architecture -``` +```text ┌─────────────────────────────────────────────────────────┐ │ LEDMatrix System │ │ ┌────────────────────────────────────────────────────┐ │ @@ -199,7 +199,7 @@ Tronbyte/Tidbyt apps are designed for **64×32 displays**. LEDMatrix automatical The plugin calculates optimal magnification based on your display: -``` +```text magnify = floor(min(display_width / 64, display_height / 32)) ``` @@ -250,7 +250,7 @@ Apps without extracted schemas can still run with default settings. ## File Structure -``` +```text LEDMatrix/ ├── plugin-repos/starlark-apps/ # Plugin source code │ ├── manager.py # Main plugin logic @@ -378,7 +378,7 @@ Cached WebP files are stored in `starlark-apps/{app-id}/cached_render.webp` Balance number of enabled apps with display duration: - 5 apps × 15s = 75s full cycle -- 20 apps × 15s = 300s (5min) cycle +- 20 apps × 15s = 300s (5 min) cycle Long cycles may cause apps to render before being displayed. diff --git a/plugin-repos/starlark-apps/manager.py b/plugin-repos/starlark-apps/manager.py index b82a256d..3da8c24d 100644 --- a/plugin-repos/starlark-apps/manager.py +++ b/plugin-repos/starlark-apps/manager.py @@ -63,7 +63,7 @@ class StarlarkApp: try: with open(self.config_file, 'r') as f: return json.load(f) - except Exception as e: + except (OSError, json.JSONDecodeError) as e: logger.warning(f"Could not load config for {self.app_id}: {e}") return {} @@ -73,7 +73,7 @@ class StarlarkApp: try: with open(self.schema_file, 'r') as f: return json.load(f) - except Exception as e: + except (OSError, json.JSONDecodeError) as e: logger.warning(f"Could not load schema for {self.app_id}: {e}") return None @@ -118,6 +118,11 @@ class StarlarkApp: if isinstance(value, str) and value.strip().startswith('{'): try: loc = json.loads(value) + except json.JSONDecodeError as e: + return f"Invalid JSON for key {key}: {e}" + + # Validate lat/lng if present + try: if 'lat' in loc: lat = float(loc['lat']) if not -90 <= lat <= 90: @@ -126,9 +131,8 @@ class StarlarkApp: lng = float(loc['lng']) if not -180 <= lng <= 180: return f"Longitude {lng} out of range [-180, 180] for key {key}" - except (json.JSONDecodeError, ValueError, KeyError) as e: - # Not a location field, that's fine - pass + except ValueError as e: + return f"Invalid numeric value for {key}: {e}" return None @@ -584,8 +588,8 @@ class StarlarkAppsPlugin(BasePlugin): fcntl.flock(lock_fd, fcntl.LOCK_UN) os.close(lock_fd) - except Exception as e: - self.logger.error(f"Error saving manifest: {e}") + except (OSError, IOError, json.JSONDecodeError, ValueError) as e: + self.logger.exception("Error saving manifest while writing manifest file", exc_info=True) # Clean up temp file if it exists if temp_file is not None and temp_file.exists(): try: @@ -651,8 +655,8 @@ class StarlarkAppsPlugin(BasePlugin): fcntl.flock(lock_fd, fcntl.LOCK_UN) os.close(lock_fd) - except Exception as e: - self.logger.error(f"Error updating manifest: {e}") + except (OSError, IOError, json.JSONDecodeError, ValueError) as e: + self.logger.exception("Error updating manifest during read-modify-write cycle", exc_info=True) # Clean up temp file if it exists if temp_file is not None and temp_file.exists(): try: diff --git a/plugin-repos/starlark-apps/pixlet_renderer.py b/plugin-repos/starlark-apps/pixlet_renderer.py index 9efe5ced..40f8f59d 100644 --- a/plugin-repos/starlark-apps/pixlet_renderer.py +++ b/plugin-repos/starlark-apps/pixlet_renderer.py @@ -41,9 +41,9 @@ class PixletRenderer: self.pixlet_binary = self._find_pixlet_binary(pixlet_path) if self.pixlet_binary: - logger.info(f"Pixlet renderer initialized with binary: {self.pixlet_binary}") + logger.info(f"[Starlark Pixlet] Pixlet renderer initialized with binary: {self.pixlet_binary}") else: - logger.warning("Pixlet binary not found - rendering will fail") + logger.warning("[Starlark Pixlet] Pixlet binary not found - rendering will fail") def _find_pixlet_binary(self, explicit_path: Optional[str] = None) -> Optional[str]: """ @@ -280,7 +280,13 @@ class PixletRenderer: "-m", str(magnify) ]) - logger.debug(f"Executing Pixlet: {' '.join(cmd)}") + # Build sanitized command for logging (redact sensitive values) + sanitized_cmd = [self.pixlet_binary, "render", star_file] + if config: + config_keys = list(config.keys()) + sanitized_cmd.append(f"[{len(config_keys)} config entries: {', '.join(config_keys)}]") + sanitized_cmd.extend(["-o", output_path, "-m", str(magnify)]) + logger.debug(f"Executing Pixlet: {' '.join(sanitized_cmd)}") # Execute rendering safe_cwd = self._get_safe_working_directory(star_file) @@ -373,6 +379,7 @@ class PixletRenderer: # Extract get_schema() function body schema_body = self._extract_get_schema_body(content) if not schema_body: + logger.debug(f"No get_schema() function found in {file_path}") return None # Extract version @@ -397,6 +404,7 @@ class PixletRenderer: if bracket_count != 0: # Unmatched brackets + logger.warning(f"Unmatched brackets in schema fields for {file_path}") return {"version": version, "schema": []} fields_text = schema_body[fields_start_match.end():i-1] diff --git a/web_interface/static/v3/plugins_manager.js b/web_interface/static/v3/plugins_manager.js index b245774e..3a8216ef 100644 --- a/web_interface/static/v3/plugins_manager.js +++ b/web_interface/static/v3/plugins_manager.js @@ -4,7 +4,7 @@ const safeLocalStorage = { getItem(key) { try { if (typeof localStorage !== 'undefined') { - return safeLocalStorage.getItem(key); + return localStorage.getItem(key); } } catch (e) { console.warn(`safeLocalStorage.getItem failed for key "${key}":`, e.message); @@ -14,7 +14,7 @@ const safeLocalStorage = { setItem(key, value) { try { if (typeof localStorage !== 'undefined') { - safeLocalStorage.setItem(key, value); + localStorage.setItem(key, value); return true; } } catch (e) { @@ -7924,24 +7924,27 @@ setTimeout(function() { `; }).join(''); - // Add delegated event listeners for install and view buttons - grid.addEventListener('click', function(e) { - const button = e.target.closest('button[data-action]'); - if (!button) return; + // Add delegated event listener only once (prevent duplicate handlers) + if (!grid.dataset.starlarkHandlerAttached) { + grid.addEventListener('click', function handleStarlarkGridClick(e) { + const button = e.target.closest('button[data-action]'); + if (!button) return; - const card = button.closest('.plugin-card'); - if (!card) return; + const card = button.closest('.plugin-card'); + if (!card) return; - const appId = card.dataset.appId; - if (!appId) return; + const appId = card.dataset.appId; + if (!appId) return; - const action = button.dataset.action; - if (action === 'install') { - window.installStarlarkApp(appId); - } else if (action === 'view') { - window.open('https://github.com/tronbyt/apps/tree/main/apps/' + encodeURIComponent(appId), '_blank'); - } - }); + const action = button.dataset.action; + if (action === 'install') { + window.installStarlarkApp(appId); + } else if (action === 'view') { + window.open('https://github.com/tronbyt/apps/tree/main/apps/' + encodeURIComponent(appId), '_blank'); + } + }); + grid.dataset.starlarkHandlerAttached = 'true'; + } } // ── Filter UI Updates ───────────────────────────────────────────────────