Repository navigation
feat(web): browse downloads without the server - #226
mia-riezebos wants to merge 3 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Important
Interrupted artwork saves can leave empty files that subsequent downloads permanently reuse. Fix the artwork reuse check before merging.
Reviewed changes across offline session fallback, download metadata and artwork persistence, service-worker responses, and the Downloads/detail UI.
- Saved download data: Download records retain Item details and shared artwork, with cleanup when the final referencing download is deleted.
- Offline shell and detail: Cached session/library data opens the shell without the server; completed downloads supply detail pages with server-only actions hidden.
- Downloads browsing: Movies and Shows render with artwork, Season grouping, Episode ordering, detail links, and transfer controls.
- Verification: All 288 web unit tests pass and Svelte checks report no errors or warnings. A temporary regression test reproduced the empty-artwork reuse bug and was removed afterward; the new browser integration test was reviewed but not rerun. Offline playback is explicitly deferred to #175.
gpt-6.1-sol | 𝕏
| await folder.getFileHandle(id); | ||
| continue; |
There was a problem hiding this comment.
[P2] Retry artwork files left empty by an interrupted save
Creating a file handle with { create: true } creates an empty entry before the writable commits its bytes, so a failed write or tab termination can leave a zero-byte file behind. This existence-only check skips that file on every subsequent attempt, leaving later sibling Episode downloads with broken artwork offline even when they finish successfully.
Technical details
## Affected sites
- `saveArtwork` checks only handle existence before skipping a saved copy.
- The later `getFileHandle(id, { create: true })` creates the entry before `createWritable()`/`write()`/`close()` completes.
- `savedArtwork` and the service worker accept this empty file as the offline response.
## Required outcome
- Reuse only a successfully committed, non-empty artwork file; an empty placeholder must remain eligible for saving.
- Add a regression test that creates an artwork handle without committing content, completes a download referencing it, and verifies that the artwork is fetched and saved.
## Reproduction
Using the existing `FakeDir`, precreate `downloads/artwork/poster` with `getFileHandle("poster", { create: true })`, then start a download whose detail references that poster. The media download reaches `complete`, but the artwork fetch list stays empty and the poster remains zero bytes. A temporary test asserting the expected artwork fetch failed on this head.The file-creation site precedes the commit. MDN documents that getFileHandle creates a missing file and that createWritable changes are not reflected until close.
|
The failed CI job has one failure: Task list (1/3 completed)
|
612ea60 to
434f40c
Compare
There was a problem hiding this comment.
ℹ️ No new issues found in this delta. The earlier interrupted-artwork-save finding remains unresolved, so this is not an approval.
Reviewed changes since the previous review, including the rebase onto feat/178-offline-image-subs and the offline playback links.
- Connected offline Play: Added
&offlineto the detail page's primary and per-Version Play links so they open the downloaded file rather than an online session. - Preserved download integration: Kept stored subtitle data, subtitle cleanup, and codec-gated Downloads playback controls alongside the offline browsing changes on the updated base.
- Extended browser coverage: Added a check that the offline detail page's Play link loads video while the server is stopped.
Verification: all 301 web unit tests pass; Svelte diagnostics report 0 errors and 0 warnings after bun install --frozen-lockfile. The browser integration test was reviewed but not rerun because ffmpeg and ffprobe are unavailable in this environment.
gpt-6.1-sol | 𝕏
|
Pushed signed commit e8754da to PR #226: the offline-browse test now waits for artwork cleanup, and browser polling awaits asynchronous predicates instead of treating unresolved Promises as success. Reproduced the exact assertion failure under index-lock contention and verified the fix; ten consecutive browse runs, download-resume coverage, lint, type checks, build, and Task list (3/3 completed)
|
A download now saves its Item's detail and artwork next to the file, so the Downloads page and the pages of downloaded Movies and Episodes open with the server stopped. - The download record keeps the Item detail; the transfer saves its poster, backdrop, logo and thumb (and its Show's) under downloads/artwork. Deleting the last download that draws an artwork deletes it, under the index lock. - The service worker answers /api/artwork from those copies when the server can't. - The shell keeps the last signed-in caller and libraries, so it opens offline; sign out forgets them. - Downloads groups Movies, then Shows by Season and Episode, with artwork, each row linking to its page. - Offline, a downloaded Item's page shows its saved detail with Play; any other says it needs the connection. - Restore treats a record whose whole file is on disk as finished, so a tab closed right after the last byte no longer shows it paused. Closes #179 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Play on a downloaded Item's offline page opened the server's player, which said the server was unreachable. It now opens the download. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
47f1c4e to
ed0090b
Compare
e8754da to
9f9b55a
Compare
There was a problem hiding this comment.
ℹ️ No new issues found in this delta. The earlier interrupted-artwork-save finding remains unresolved, so this is not an approval.
Reviewed changes since the prior review: the two feature commits replay unchanged on the updated base; the new commit fixes browser-test synchronization.
- Updated asynchronous polling: Changed
Page.waitForto await a condition's resolved value before testing its truthiness, preserving synchronous conditions and retrying rejected evaluations. - Waited for artwork cleanup: Added an OPFS condition after deleting the Movie so the test waits for its artwork to disappear while the Show poster remains, rather than treating row removal as cleanup completion.
Verification: all 32 download tests passed. Direct checks of the actual helper in Chrome passed for synchronous conditions, asynchronous false-to-true polling, rejection retries, and timeouts. The full offline-browser integration test was not rerun because ffmpeg and ffprobe are unavailable.
gpt-6-astra | 𝕏

With the server unreachable, the web app showed only "Server unreachable": the Downloads page, and the pages of what you downloaded, all needed the API. This is #179 of PRD #173.
A download now saves the Item's detail and artwork next to its file, and the app reads them when the server can't answer.
downloads/artwork/in OPFS, once per artwork id. Deleting the last download that draws an artwork deletes it, under the index lock, so two Episodes of one Show share their Show's poster safely./api/artwork/*from the network, and from the saved copy when the network fails or a proxy answers 5xx. Every artwork component works unchanged. It still caches no API responses.localStorage(thalia.session) after each successful load, and stand in when the server is unreachable. Sign out forgets them./playgets the same fallback, since it sharesloadSession.Closes #179
Stacked on #239
This PR now stacks on #239 (offline playback, #225 → #233 → #237 → #239), so the two offline stacks are one. Play on an offline page opens the download in that player (
/play/{itemId}?version={downloadedVersionId}&offline), the same link as Play on the Downloads page. A download record keeps both its saved detail (this PR) and its saved subtitle files (#233).Screenshots
The fixture artwork is flat blue on purpose. Before: the downloads list from #208 (1440 light); offline, every page here showed the full-screen "Server unreachable".
Downloads, server stopped:
Detail pages, server stopped:
Tests
apps/server/src/api/offline-browse-browser.test.ts(new, headless Chromium): downloads a movie and two Episodes through the Download button, stops the server, then checks that Downloads lists them grouped as Movies / Severance / Season 1 in episode order with both posters loaded from OPFS, that the movie's page shows its title and its Play link plays the download with the server still down, that a page that isn't downloaded says "Not available offline", and that deleting the movie removes its artwork and keeps the Show's. 3 passes in a row, alongsidedownloads-browser.test.ts.lib/downloads/library.test.ts(new): grouping order, Specials last, legacy records last; an offline detail lists only the downloaded Version, and only for finished downloads.lib/downloads/store.test.ts: artwork is saved once per id and deleted with the last download that draws it; restore finishes a record whose whole file is on disk.bun test: 301 pass on the joined stack.bun run check: 0 errors, 0 warnings.bun run lint: 0 errors, the 9 CSS warnings already on main.Made with claude-opus-5.5 using Claude Code in T3 Code.
🤖 Generated with Claude Code