mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-09 07:25:44 +02:00
fix(static): serve /fonts/*.woff2 — self-hosted Inter font (#1460 follow-up)
The browser console logged "downloadable font: rejected by sanitizer" for inter-latin.woff2 on every load. The #1460 PWA fix added @font-face rules pointing at /fonts/inter-latin.woff2 and bundled the woff2 files into static/fonts/, but main.py only mounts /assets, /img and /icons as static directories. With no /fonts mount, /fonts/*.woff2 fell through to the SPA catch-all and returned index.html with 200 OK; the browser's OpenType sanitizer rejected the HTML-as-a-font. Add a /fonts StaticFiles mount alongside /img and /icons. The woff2 files themselves are valid (verified — Inter variable, latin and latin-ext subsets). Also bump the service worker STATIC_CACHE version (v26 -> v27). sw.js lists the two font URLs in STATIC_ASSETS, and cache.addAll() treats the 200 OK HTML as a successful fetch — so it had cached index.html under the font URLs and served it cache-first. The version bump makes the activate handler purge the poisoned cache and re-fetch the real fonts.
This commit is contained in:
@@ -20,6 +20,7 @@ All notable changes to Bambuddy will be documented in this file.
|
||||
- **PyJWT CVE-2025-45768 (PYSEC-2025-183 / GHSA-65pc-fj4g-8rjx): permanently ignored in pip-audit** — Advisory is disputed by the PyJWT maintainers, with the advisory description literally noting *"this is disputed by the Supplier because the key length is chosen by the application that uses the library."* `fix_versions=[]` on the advisory confirms no PyJWT patch exists or will exist. Bambuddy is not affected: `backend/app/core/auth.py:184` auto-generates secrets via `secrets.token_urlsafe(64)` (~86 chars of entropy, far above any sane minimum) and the file-loaded path at `:177` rejects secrets shorter than 32 chars. Added a permanent `--ignore-vuln CVE-2025-45768` to `.github/workflows/security.yml` with an inline comment citing the file:line evidence so a future maintainer reviewing the ignore list sees why it's load-bearing. Also dropped the stale `--ignore-vuln CVE-2026-4539` for Pygments — Pygments has since shipped a patched version and the ignore is no longer load-bearing (verified: `pip-audit --ignore-vuln CVE-2025-45768` alone reports clean).
|
||||
|
||||
### Fixed
|
||||
- **Self-hosted Inter font now actually loads — `/fonts/*.woff2` was not served (#1460 follow-up)** — The browser console logged `downloadable font: rejected by sanitizer` for `inter-latin.woff2` on every page load. The #1460 PWA fix added `@font-face` rules pointing at `/fonts/inter-latin.woff2` and bundled the woff2 files into `static/fonts/`, but `main.py` only mounts `/assets`, `/img` and `/icons` as static directories — there was no `/fonts` mount. So `/fonts/*.woff2` fell through to the SPA catch-all and returned `index.html` with `200 OK`; the browser's OpenType sanitizer then rejected the HTML-as-a-font. The woff2 files themselves are valid (verified — Inter variable, latin + latin-ext subsets). **Fix**: added a `/fonts` `StaticFiles` mount alongside the existing `/img` and `/icons` mounts. Additionally, the service worker had cached the bad response: `sw.js` lists the two font URLs in `STATIC_ASSETS` and `cache.addAll()` treats the `200 OK` HTML as a successful fetch, so it stored `index.html` under the font URLs in the static cache and served it cache-first. The SW `STATIC_CACHE` version is bumped (`v26` → `v27`) so the `activate` handler purges the poisoned cache and re-fetches the real fonts on next load. The UI falls back to a system sans-serif until deployed, so there is no visible breakage — only the console warning.
|
||||
- **Library files now display the filename, not the embedded 3MF Title (#1489, reported by @needo37)** — File Manager cards, search and sort keyed off `file_metadata.print_name`, which `ThreeMFParser` lifts from the 3MF's `<metadata name="Title">`. That title is the in-app project title — generic `"Exported 3D Model"` for any Bambu Studio "Save As", a marketing title for a MakerWorld download — and almost never the filename the user actually saved. So a card for `Whatever.3mf` showed `Exported 3D Model`, and the only way to correct it was a rename round-trip (the Rename dialog's Save button is disabled while the name is unchanged, so the user had to rename to a different name and back). The slicer-output write path already dropped `print_name` for exactly this reason; the **four** other write paths that store parsed 3MF metadata onto a `LibraryFile` did not — external-folder scan, managed multipart upload, the multi-file ZIP-upload branch, and MakerWorld import. **Fix**: a shared `_without_print_name()` helper strips `print_name` from library-file metadata, applied at all four import paths (and the slicer path switched to it, so there is one rule). A `LibraryFile`'s display name is its filename; only `PrintArchive` carries a real `print_name`, and that is untouched. The now-redundant filename→`print_name` mirroring in the rename route is removed. A one-time data migration (`_migrate_drop_library_print_name`, idempotent, SQLite `json_remove` / PostgreSQL `jsonb` key-removal branched on `is_sqlite()`) clears `print_name` from rows imported before the fix, so existing libraries correct themselves without the rename workaround. No frontend change — `print_name || filename` naturally yields the filename once `print_name` is gone. **Tests**: 6 new in `test_library_print_name.py` — `_without_print_name` (strips, keeps siblings, `None` pass-through, no-op identity return, no input mutation, print-name-only → `{}`) and the migration (clears `print_name`, leaves siblings and metadata-free rows alone, idempotent). 109 library + dialect tests green; the migration's PostgreSQL branch additionally ran live against real Postgres during the integration-test app boot. Backend ruff clean.
|
||||
- **Camera: ffmpeg's stderr is now captured when an RTSP stream stalls instead of only when ffmpeg crashes (#1395, reported by @Tschipel)** — A P2S support bundle taken on 0.2.5b1 (the per-model probesize fix already applied) showed the camera still failing: ffmpeg connects, stays alive 30+ seconds, emits zero JPEG bytes, the stream's 30 s `stdout.read` times out, reconnect loop repeats — but with **no ffmpeg stderr anywhere in the log** to say why. Root cause was a diagnostic bug, not the camera path: `_read_ffmpeg_stderr` called `process.stderr.read()` (read-to-EOF). A stalled-but-still-alive ffmpeg — exactly the P2S RTSP failure mode — never closes stderr, so the read blocked until the 2 s `wait_for` timeout and returned `None`, discarding the banner + stream-analysis lines ffmpeg had already printed. ffmpeg's stderr was therefore captured *only* when it fully exited; the earlier "not enough frames to estimate rate" smoking gun was available only because ffmpeg crashed back then, and once the probesize bump turned the crash into a hang the diagnostic went dark. **Fix**: `_read_ffmpeg_stderr` now drains stderr incrementally in bounded 8 KB chunks (64 KB cap), returning whatever ffmpeg has printed so far whether or not it has exited — so a hung stream is self-describing in the next support bundle. Additionally, `generate_rtsp_mjpeg_stream` now logs the resolved per-model `probesize` / `analyzeduration` on the info-level "Starting RTSP camera stream" line (verifiable without debug logging), and the debug-level ffmpeg-command line logs the full argv with only the credential-bearing camera URL redacted, instead of hiding the entire command. No behaviour change to streaming itself — this makes the still-unresolved P2S RTSP stall diagnosable. **Tests**: 4 new in `test_camera_stderr_summary.py` cover `_read_ffmpeg_stderr` capturing output from a *running* (un-exited, no-EOF) ffmpeg — the regression — as well as the exited case, the no-stderr-pipe case, and banner-only output summarizing to `None`. 9 camera-stderr tests green; backend ruff clean.
|
||||
- **Camera diagnostic (stethoscope) was missing from the pop-out camera window (#1395, reported by @Tschipel)** — The #1395 camera-diagnostic follow-up — stethoscope icon in the control bar, **Diagnose** button in the stream-error state, `CameraDiagnoseModal` — shipped wired into `EmbeddedCameraViewer.tsx` only, the *embedded* camera mode. It was never added to `CameraPage.tsx`, the standalone window that opens at `/camera/{id}` when `camera_view_mode` is `window` (the default). The reporter's support bundle had `"camera_view_mode": "window"`, so they were on `CameraPage` the whole time and genuinely could not see the stethoscope no matter how many container rebuilds or cache clears they tried — the JS bundle did contain the `camera.diagnose` strings (they come from `EmbeddedCameraViewer`), but that component never renders in window mode. Switching to overlay mode made it appear instantly, exactly as the reporter found. **Fix**: ported the diagnostic into `CameraPage.tsx` — the `Stethoscope` control-bar button (between **Refresh** and **Fullscreen**, matching the embedded viewer), a **Diagnose** button next to **Retry** in the `streamError` block, and the `CameraDiagnoseModal` render. No new i18n keys — `camera.diagnose.*` already exist in all 9 locales. The backend per-model camera-profile fix from the same issue is view-mode-agnostic and already applied; this only makes the diagnostic reachable in the default window mode. Frontend build clean.
|
||||
|
||||
@@ -5457,6 +5457,16 @@ if app_settings.static_dir.exists() and any(app_settings.static_dir.iterdir()):
|
||||
StaticFiles(directory=app_settings.static_dir / "icons"),
|
||||
name="icons",
|
||||
)
|
||||
# Self-hosted Inter woff2 files (#1460). Without this mount /fonts/*.woff2
|
||||
# falls through to the SPA catch-all and returns index.html, which the
|
||||
# browser's font sanitizer rejects ("downloadable font: rejected by
|
||||
# sanitizer").
|
||||
if (app_settings.static_dir / "fonts").exists():
|
||||
app.mount(
|
||||
"/fonts",
|
||||
StaticFiles(directory=app_settings.static_dir / "fonts"),
|
||||
name="fonts",
|
||||
)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Bambuddy Service Worker
|
||||
const CACHE_NAME = 'bambuddy-v27';
|
||||
const STATIC_CACHE = 'bambuddy-static-v26';
|
||||
const CACHE_NAME = 'bambuddy-v28';
|
||||
const STATIC_CACHE = 'bambuddy-static-v27';
|
||||
|
||||
// Static assets to cache on install
|
||||
const STATIC_ASSETS = [
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
// Bambuddy Service Worker
|
||||
const CACHE_NAME = 'bambuddy-v27';
|
||||
const STATIC_CACHE = 'bambuddy-static-v26';
|
||||
const CACHE_NAME = 'bambuddy-v28';
|
||||
const STATIC_CACHE = 'bambuddy-static-v27';
|
||||
|
||||
// Static assets to cache on install
|
||||
const STATIC_ASSETS = [
|
||||
|
||||
Reference in New Issue
Block a user