fix: address CodeRabbit review findings

Verified each finding against the code; fixes for the valid ones:

- install_dependencies_apt.py: the installer listed 'freetype', but the
  declared dependency is freetype-py — an apt miss would pip-install the
  wrong PyPI package. Now installs freetype-py with an import-name
  mapping (pre-existing bug, surfaced by the review).
- api_v3.py: pixel_mapper_config is validated as a string before being
  saved to display.hardware (JSON callers could previously store an
  object/list the matrix library can't use).
- .cursorrules: the Plugin Loading Process and File Organization
  sections still said discovery scans plugins/ — now consistent with the
  corrected overview (configured directory, default plugin-repos/).
- README.md: removed the stale '(except the core calendar)' claim — no
  core calendar exists in src/ — and qualified the plugin inventory
  (official plugins in the monorepo; third-party from their own repos).
- CONFIG_REFERENCE.md: hardware_mapping now shows the code fallback
  (adafruit-hat-pwm) alongside the template value.
- PLUGIN_REGISTRY_SETUP_GUIDE.md: check_plugin.py takes --plugin, not a
  positional id.
- scripts/fix_perms/fix_*.sh: exec bits set so the documented
  'sudo ./...' invocations work.
- Guard tests hardened: template guard now catches multi-line
  render_template() calls; widget guard parses actual <script> src
  values and fails if the widgets dir goes missing; type hints and
  docstrings added per repo coding guidelines.

Skipped with reasons (noted on the PR): limit_refresh_rate_hz 100-vs-90
is documented as intentional in CONFIG_REFERENCE.md; the psutil comment
already names the enforcing manifest; docs/archive/ findings are out of
scope per the docs policy (archive may rot).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXb4mKcAkVaxkeTb3YnAdr
This commit is contained in:
Claude
2026-08-06 01:32:23 +00:00
parent 66336fe160
commit b428ae3eae
14 changed files with 82 additions and 42 deletions
+30 -12
View File
@@ -6,12 +6,19 @@ config schema declares that widget (the field renders as an empty container
that polls the registry forever). base.html's widget list is maintained by
hand, so this test keeps it honest.
"""
import re
from pathlib import Path
from typing import Set
PROJECT_ROOT = Path(__file__).resolve().parent.parent
WIDGETS_DIR = PROJECT_ROOT / 'web_interface' / 'static' / 'v3' / 'js' / 'widgets'
BASE_HTML = PROJECT_ROOT / 'web_interface' / 'templates' / 'v3' / 'base.html'
# Matches url_for('static', filename='...') inside actual <script> tags.
SCRIPT_SRC_RE = re.compile(
r"""<script\s[^>]*src="\{\{\s*url_for\(\s*'static'\s*,\s*filename='([^']+)'\s*\)\s*\}\}[^"]*"""
)
# Files that must NOT be script-included, with the reason.
ALLOWLIST = {
# Documentation example (docs/widget-guide.md); registers the name
@@ -20,14 +27,26 @@ ALLOWLIST = {
}
def test_every_widget_script_is_included_in_base_html():
def _included_widget_scripts() -> Set[str]:
"""Return widget JS basenames referenced by real <script> tags in base.html."""
base_html = BASE_HTML.read_text(encoding='utf-8')
missing = []
for js_file in sorted(WIDGETS_DIR.glob('*.js')):
if js_file.name in ALLOWLIST:
continue
if f'v3/js/widgets/{js_file.name}' not in base_html:
missing.append(js_file.name)
return {
Path(filename).name
for filename in SCRIPT_SRC_RE.findall(base_html)
if filename.startswith('v3/js/widgets/')
}
def test_every_widget_script_is_included_in_base_html() -> None:
"""Every non-allowlisted widget file must be loaded by a <script> tag."""
assert WIDGETS_DIR.is_dir(), f'Widget directory missing: {WIDGETS_DIR}'
included = _included_widget_scripts()
assert included, 'No widget <script> tags found in base.html — regex or template drift?'
missing = [
js_file.name
for js_file in sorted(WIDGETS_DIR.glob('*.js'))
if js_file.name not in ALLOWLIST and js_file.name not in included
]
assert not missing, (
'Widget files exist but are never <script>-included in base.html '
'(plugins declaring these widgets get blank config fields): '
@@ -37,11 +56,10 @@ def test_every_widget_script_is_included_in_base_html():
)
def test_allowlisted_widgets_are_not_included():
base_html = BASE_HTML.read_text(encoding='utf-8')
wrongly_included = [
name for name in ALLOWLIST if f'v3/js/widgets/{name}' in base_html
]
def test_allowlisted_widgets_are_not_included() -> None:
"""Allowlisted (must-not-load) widget files must stay out of base.html."""
included = _included_widget_scripts()
wrongly_included = [name for name in ALLOWLIST if name in included]
assert not wrongly_included, (
'Allowlisted (must-not-load) widget files are script-included in '
'base.html: ' + ', '.join(wrongly_included)