this change replace original window.fetch by a wrapper that check any 401 response, is so it will request a refresh token
this solve current issue with Favorites & Recent sections that give blank page when token is expired
- exclusion of requests to other domain (401 will not be handled here)
- security with shares /api/s is not handled
- check with CSRF, no risk
- change worker: do not cache html pages (not necessary)
- remove use of window.XXX and maximize import/export,
this will provide more clarety,
show circular dependencies + you will benefit IDE help
Replace the innerHTML full-rebuild in _render() with two paths:
- _renderFull(): used for first load, group-mode change, and deletions
- _appendBatch(n): append-only for infinite-scroll pages — O(batch)
instead of O(total). Existing <img> nodes are never destroyed,
eliminating the visual flash and unnecessary DOM churn.
Also:
- Extract _renderTile() helper (DRY tile HTML generation)
- Extract _observeSentinel() helper
- Scope _setupVideoThumbnails(startIndex) to only process new tiles
- Add data-group attribute on headers for efficient CSS.escape lookup
- Fix stale WebP references in comments (now JPEG)
- Add virtual scrolling idea to TODO-LIST.md for future evaluation
Replace all 3 ImageFormat::WebP encode sites with JpegEncoder q=80.
Update fast-path to detect JPEG SOI instead of RIFF/WEBP magic.
Change file extension .webp -> .jpg, Content-Type headers, and
browser toBlob. Remove unused ImageFormat import and stale comments.
The webp feature stays for DECODING uploaded WebP images.
Schema (media_sort_date denormalization):
- Add media_sort_date column to storage.files with DEFAULT created_at
- Add trigger sync_media_sort_date: when file_metadata is upserted,
copies COALESCE(captured_at, created_at) into files.media_sort_date
- Add partial index idx_files_media_timeline on (user_id, media_sort_date DESC)
WHERE NOT is_trashed AND media type -- enables Index Scan + Limit (no Sort)
- copy_folder_tree now copies media_sort_date for copied files
- Remove dead idx_file_metadata_captured (no longer needed)
Query optimization (list_media_files):
- Rewrite to use fi.media_sort_date instead of COALESCE(fm.captured_at,...)
- Eliminates LEFT JOIN file_metadata -- one fewer table touch
- Plan: Limit to Index Scan O(LIMIT) instead of Sort O(N)
Video thumbnail pre-scaling (client + server):
- JS: pre-scale canvas to max 400px before toBlob -- 22x less RAM, 15x less BW
- Rust: fast-path in store_external_thumbnail -- if payload is already
WebP with dims within max_dim, store as-is (zero decode, zero encode)
The double-submit CSRF middleware blocks all mutating requests
without the X-CSRF-Token header. photos.js was uploading video
thumbnails via PUT without it, causing a flood of 403 errors.
- Add getCsrfHeaders() spread to the fetch headers
- Add credentials: 'same-origin' to ensure cookie is sent
Backend:
- 204 response: add Cache-Control: no-store so browser never caches
'no thumbnail yet' — next GET after PUT upload gets the WebP
Frontend:
- _videoThumbCache (Map): persists fileId → URL across re-renders,
so switching tabs reuses cached URLs instantly (no re-decode)
- Render: videos with cached URL skip the 204/error/decode cycle
- After PUT succeeds: swap blob URL → server ?v=1 URL so blob is GC'd
- Concurrency throttle: max 3 simultaneous video decodes to avoid
overwhelming network + CPU when gallery has many videos
- Decode queue: pending videos processed as slots free up
Backend:
- get_thumbnail: for non-images, serve cached thumb or 204 (not 400)
- store_external_thumbnail: validate + re-encode to WebP + persist
- get_cached_thumbnail: memory → disk lookup without generation
- PUT /api/files/{id}/thumbnail/{size} endpoint for client uploads
- ThumbnailPort trait: add get_cached_thumbnail + store_external_thumbnail
Frontend (photos.js):
- On <img> error for video tiles, use <video> + <canvas> to extract
a frame at 25% duration using browser's native codec
- Show frame immediately via blob URL
- Fire-and-forget PUT to server for permanent caching
- Subsequent visits serve cached WebP instantly (no re-extraction)
Zero server-side dependencies — CPU distributed across clients.
Backend: new GET /api/photos endpoint with cursor-based pagination that
queries image/video files sorted by EXIF captured_at (falling back to
created_at), joining file_metadata for sort dates.
Frontend: dense photo grid grouped by day with lazy-loaded thumbnails,
IntersectionObserver infinite scroll, multi-select with batch
download/delete, and a full-screen lightbox with prev/next navigation,
EXIF metadata display, and download/favorite/delete toolbar.
Includes navigation wiring, CSS (with dark theme), and i18n translations
for all 9 locales.
Replace ~50 inline style="" attributes with CSS classes, move 3 inline
<script> blocks to external JS files, replace all inline event handlers
(onclick, onerror) with addEventListener, and remove createElement('style')
from icons.js. All changes support the strict CSP policy (style-src 'self';
script-src 'self') without weakening it.
- 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
- Fix recursive CTE: add missing RECURSIVE keyword in move_to_trash and restore_from_trash SQL queries (relation 'descendants' does not exist)
- Fix folder deletion: delete descendant files before folder to avoid 'duplicate key violates unique constraint idx_files_unique_name_at_root'
- Simplify trash model: only mark the folder as trashed, not child files (implicit trash via parent)
- Update trash_items view: filter to show only top-level trashed items
- Update schema.sql: change files.folder_id FK from ON DELETE SET NULL to ON DELETE CASCADE
- Fix trash view icons: folders and files now show correct visual icons (folder-icon, pdf-icon, etc.) in trash view
- Frontend refactoring: extract inline CSS/JS from admin.html and profile.html into dedicated external files
- Frontend cleanup: replace all inline style attributes with CSS classes
- Frontend cleanup: replace style.display JS
- Fix recursive CTE: add missing RECURSIVE keyword in move_to_trash and restore_from_trash SQL queries (relation 'descendants' d