Root cause of folder uploads "freezing at ~95%": the global Content-Security-
Policy `script-src` was `'self'` + inline-script hashes with NO
`'wasm-unsafe-eval'`. Chromium therefore blocked `WebAssembly.instantiate`
("Wasm code generation disallowed by embedder"), so the vendored BLAKE3/FastCDC
WASM threw on instantiation — both on the main thread (instant by-hash uploads
and the batch dedup check) and inside the delta-upload worker. Every file then
fell back to a plain byte upload, and the backend logs showed 0 check-batch /
0 negotiate calls. Large files (32 MB service logs) compounded it and the
session token expired mid-upload, so the last handful failed.
- web/mod.rs: add `'wasm-unsafe-eval'` to `script-src`. WASM-only, safe variant
— does NOT enable `eval()`/`new Function()`. Restores instant uploads, delta
(sub-file dedup), and the client hashing the idempotent re-upload relies on.
- deltaUpload.ts: liveness watchdog on the delta worker. A healthy worker posts
progress sub-second; if it goes silent for 20 s it is wedged (WASM init or
chunking hung without throwing) — disable delta for this file AND every later
one so they fall straight through to a plain upload instead of each burning
the full 120 s+ delta timeout. Defense-in-depth so a broken WASM path can
never again freeze an upload for minutes.
cargo test: pass. npm run check: clean, 58 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two issues kept the Photos → Places map blank once the SPA could boot:
- CSP `worker-src 'self'` blocked MapLibre GL, which spawns its web worker
from a blob: URL, so the map never constructed. Allow `worker-src
'self' blob:` ('self' still covers same-origin workers like delta-upload).
- `checkBasemap()` trusted `res.ok`, but the SPA fallback serves index.html
(HTTP 200, text/html) for any missing path — so a missing basemap.pmtiles
read as "present", and pmtiles.js then choked on HTML ("Wrong magic
number for PMTiles archive"). Reject text/html responses so an absent
basemap falls back cleanly to the themed blank style.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The global `script-src 'self'` CSP header blocked every inline <script>,
so the SvelteKit hydration bootstrap never ran and the SPA never mounted
(blank page behind the splash spinner). The anti-FOUC theme init in
app.html was blocked too.
Instead of weakening the policy with 'unsafe-inline', the backend now
builds `script-src` at startup from 'self' plus a SHA-256 hash of every
inline <script> in the served HTML shells. The policy stays strict (no
'unsafe-inline' for scripts) and the hashes are recomputed from the built
assets on each startup, so a frontend rebuild needs no header edit — even
though SvelteKit's bootstrap hash changes every build.
- web::content_security_policy builds the header; web::resolve_static_path
is extracted so serving and hashing read the exact same bytes.
- byte-exact inline-script extraction (skips src= externals), unit-tested
against a known SHA-256 vector and extraction edge cases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Share links now point to /s/{token} (was /api/s/{token}) and render a
proper HTML page instead of raw JSON.
Changes:
- static/share.html: standalone public share page
- static/css/views/share-public.css: share page styles
- static/js/views/public/publicShare.js: client-side logic that fetches
share metadata via /api/s/{token}, handles password-protected shares,
and renders file download / folder info
- build.rs: include share.html in the HTML embed pipeline
- web/mod.rs: serve /s/{token} route (unauthenticated)
- share_dto.rs: generate URLs as /s/{token} instead of /api/s/{token}
- share_handler.rs: new download_shared_file() handler that validates the
share token and streams file content without requiring authentication
- routes.rs: mount GET /api/s/{token}/download (public, uses AppState)
Fix all clippy lints (collapsible if, clone on Copy, needless borrow,
redundant bindings, unused params) and apply rustfmt across the codebase.
Update test mocks to match Uuid-based trait signatures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- JWT secret auto-generates and persists to <STORAGE_PATH>/.jwt_secret
- Remove setup token: first admin setup is open until system initialized
- Fix schema.sql: move CREATE EXTENSION pg_trgm/ltree to top
- Update login UI and auth.js to remove setup token fields
- Add breadcrumb navigation with folder hierarchy display
- Create setCurrentSection() helper to centralize view state management
- Derive nav item section dynamically from DOM data-i18n attribute
- Remove remnant /shared page and consolidate to SPA sharedView
- Add search input to sharedView for client-side filtering
- Fix 'Go to Files' button to use switchToFilesView()
- Add CompressionLayer to web router for gzip compression of JS/CSS/JSON/SVG
- Add Cache-Control header (7 days + stale-while-revalidate) via SetResponseHeaderLayer
- Add 'set-header' feature to tower-http dependency
- Result: 67-85% reduction in transfer size for all static assets
Standardize code formatting across all 173 Rust source files
using rustfmt. No functional changes - purely cosmetic.
This establishes a consistent code style baseline for the
project going forward.
Add server-side routes for /profile, /admin, and /shared that
serve their respective HTML pages directly (same pattern already
used for /login). Updated all frontend references to use clean
URLs instead of .html extensions.
Fixes#99