Files
spectra/bug-tracking.md
Your Name 99971b804d fix bugs
2026-06-25 21:31:35 -06:00

7.6 KiB
Raw Blame History

Bug Tracking

Status Legend

  • Fixed — resolved and verified
  • Open — not yet addressed
  • Wontfix — accepted design/known limitation

Critical

1. All settings forms broken (send URL-encoded, API expected JSON)

Fixed: Every htmx form on the settings page (saturation, orientation, MQTT, schedule, Unsplash) sent URL-encoded form data (application/x-www-form-urlencoded) but the /api/config/<section>/<key> PUT endpoint used request.get_json(force=True, silent=True). URL-encoded data is not valid JSON, so get_json returned None → the endpoint aborted with 400. Result: every single settings form returned 400 in the browser. The MQTT checkbox bug (old #1) was a symptom of this larger issue.

Fix (spectra/web/server.py): api_config_set() now tries JSON first, then falls back to request.form.get("value"). A _coerce_form_value(raw, current) helper infers the correct Python type (bool/int/float/str) from the form string by comparing against the current config value's type.

2. API config tests wrote to real ~/.config/spectra/config.yaml

Fixed: ConfigManager._write() resolved the config path by searching CONFIG_SEARCH_PATHS, and when no file existed (test environment), fell back to ~/.config/spectra/config.yaml. Every test that called PATCH /api/config or PUT /api/config/<section>/<key> wrote test data into the real user's config file — potential data corruption.

Fix (tests/conftest.py): The app fixture now writes a minimal config.yaml into the temp directory and passes its path to create_app(config_path=...). The ConfigManager writes back to the same temp file.


High

3. _lookup_image_path crashes if DB/table doesn't exist

File: spectra/mqtt.py:1925

Problem: When the web server hasn't created the database yet (or the image table is missing), _lookup_image_path raises sqlite3.OperationalError: no such table: image. The MQTT message handler catches this in paho, but the error is unhandled at the application level.

Fixed: Wrapped the conn.execute() call in try/except sqlite3.OperationalError, logging a warning and returning None.

4. _MIME_MAP dict redefined on every file download request

File: spectra/web/server.py:240

Problem: The api_image_file() route handler defined a _MIME_MAP dict inside the function body on every request. This is a minor performance issue — dictionary creation is cheap, but the dict is constant data that should be a module-level constant.

Fixed: Moved _MIME_MAP to module level.

File: spectra/web/templates/settings.html:138

Problem: The settings template has <a href="/docs/mqtt.md">MQTT docs</a>. No Flask route serves this path, so browsers get a 404.

Fixed: Replaced with text: <code>docs/mqtt.md</code> (no link).

6. _generate_thumbnail leaks file descriptor

File: spectra/web/server.py:400406

Problem: PILImage.open(src) without a context manager leaves the file handle open until garbage collection. On long-running servers serving many thumbnails, this can exhaust file descriptors.

Fixed: Changed to with PILImage.open(src) as img:.

7. Dead code: TRIGGER_PATH and _trigger_path()

File: spectra/web/server.py:6970

Problem: app.config["TRIGGER_PATH"] was set but never read by any code. The _trigger_path() function was only used for that config key.

Fixed: Removed both the config key and the function.

8. Redundant section_data variable in api_config_set

File: spectra/web/server.py:122124

Problem: section_data = cm.config.get(section, {}) was assigned but only used for isinstance(section_data, dict) check. The old redundant mutation section_data[key] = data["value"] was already removed in a previous fix, leaving the variable as dead code.

Fixed: Removed the section_data check entirely — cm.set_nested() handles missing sections.


Medium

9. api_display_status always reports simulate: false

File: spectra/web/server.py:148

Problem: cm.get("display", "simulate") reads a config key that doesn't exist (display.simulate is set via CLI --simulate, not in the config file). The field always reports false even when the display loop runs with --simulate.

Fixed: Removed the simulate field from the API response and the dashboard template. The web server and CLI are separate processes; the web server cannot know the display loop's --simulate state.

10. _assert_safe_path TOCTOU race

File: spectra/web/server.py:319323

Problem: The path safety check (real_file.startswith(real_store + os.sep)) and the subsequent send_file() are not atomic. A symlink could be swapped in between the check and the file read. An attacker needs write access to the uploads directory to exploit this.

Status: Wontfix — requires filesystem write access to exploit; mitigation would add complexity with minimal security gain.

11. No cascade delete on Rotation FK

File: spectra/web/models.py:31

Problem: Rotation.image_id has a foreign key to image.id but no ondelete="CASCADE". Image deletion manually cleans up rotation entries (Rotation.query.filter_by(image_id=...).delete()). If a new deletion path is added without this cleanup, the FK constraint raises an error.

Status: Wontfix — explicit cleanup in the single delete route is sufficient for current code. Add cascade if new deletion paths are added.


Low

12. Object URLs never revoked in upload page

File: spectra/web/templates/upload.html:156

Problem: URL.createObjectURL(file) creates blob URLs for file previews but never releases them with URL.revokeObjectURL(). For typical sessions (a few files) the memory impact is negligible.

Status: Wontfix — mitigated by the upload page: when a file's upload completes or fails, the updateFileStatus function checks badgeClass === 'badge-ok' || badgeClass === 'badge-err' and calls URL.revokeObjectURL(blobUrl) (lines 180183). Blob URLs are already cleaned up per-file.

13. Unsplash filename collision on duplicate fetch

File: spectra/library.py:56

Problem: Original filename f"unsplash_{unsplash_id}.png" could collide if the same photo is fetched twice. Already fixed in previous session: filename now uses f"unsplash_{unsplash_id}_{uuid.uuid4().hex[:8]}.png" and a dedup check on unsplash_id prevents duplicate DB records.


Previously Fixed

Old #1 — MQTT checkbox can never be unchecked

Fixed: Hidden <input type="hidden" name="value" value="false"> added before the checkbox. Resolved as a side-effect of Bug #1 (form data now properly accepted with type coercion).

Fixed: Delete button uses hx-target="closest article" hx-swap="delete" with hx-confirm. The card is removed from the DOM directly.

Old #3 — MqttClient.reconfigure() calls __init__ directly

Fixed: Extracted _setup() and _create_client() methods from __init__, called by both __init__ and reconfigure().

Old #4 — Unsplash filename collision

Fixed: See #13 above.

Old #5 — Object URLs never revoked

Fixed: See #12 above — already handled in per-file cleanup.

Old #6 — Dead CSS #upload-result:empty

Fixed: Selector removed from app.css.

Old #8 — api_config_set double-mutates config

Fixed: Redundant section_data[key] = data["value"] mutation removed.

Fixed: tests/test_gallery.py added with upload-delete flow E2E test.