# 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/
/` 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/
/` 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:19–25` **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. ### 5. Dead link `/docs/mqtt.md` in settings page **File:** `spectra/web/templates/settings.html:138` **Problem:** The settings template has `MQTT docs`. No Flask route serves this path, so browsers get a 404. **Fixed:** Replaced with text: `docs/mqtt.md` (no link). ### 6. `_generate_thumbnail` leaks file descriptor **File:** `spectra/web/server.py:400–406` **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:69–70` **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:122–124` **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:319–323` **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 180–183). 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 `` added before the checkbox. Resolved as a side-effect of Bug #1 (form data now properly accepted with type coercion). ### Old #2 — Gallery delete shows "Saved" **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. ### Old #9 — Gallery delete test missing **Fixed:** `tests/test_gallery.py` added with upload-delete flow E2E test.