fix(anime): scope preferences, paginate sources, harden installs

- Preference store keys entries by extension package + bridge source id; legacy unscoped entries are discarded once instead of being handed to whichever extension asks first
- Source picker's "Load more" appends the next page without duplicating streamed results
- Repository index fetches and subtitle/APK downloads now time out and are size-bounded instead of hanging or growing unbounded
- APK installs are staged to a temp file and renamed into place
- Stream metadata lookup matches the requested path, not only the currently playing one
- Reworked animeui into browse-state/detail-panel/panels.css modules
- Reverted premature CHANGELOG unreleased entries; refreshed anime-browser docs
This commit is contained in:
2026-08-02 00:50:14 -07:00
parent d76e3c2a39
commit c2917ac1a5
52 changed files with 2471 additions and 1598 deletions
+45
View File
@@ -120,6 +120,51 @@ test('a failed download keeps its url so the episode still plays', async () => {
assert.deepEqual(io.removed, []);
});
test('an oversized streamed subtitle stops early and falls back to its remote url', async () => {
const io = fakeIo({});
const logged: string[] = [];
let chunksRead = 0;
let buffered = false;
const chunk = new Uint8Array(20 * 1024 * 1024);
const body = new ReadableStream<Uint8Array>(
{
pull(controller) {
chunksRead += 1;
controller.enqueue(chunk);
},
},
{ highWaterMark: 0 },
);
io.fetch = async () => ({
ok: true,
status: 200,
body,
async arrayBuffer() {
buffered = true;
throw new Error('stream should not be buffered');
},
});
const result = await cacheSubtitleTracks({
tracks: [{ url: 'http://bridge/sub/oversized', lang: 'Japanese' }],
io,
log: (message) => logged.push(message),
});
assert.equal(buffered, false);
assert.equal(chunksRead, 2);
assert.equal(result.dir, null);
assert.deepEqual(result.tracks, [
{
url: 'http://bridge/sub/oversized',
lang: 'Japanese',
sourceUrl: 'http://bridge/sub/oversized',
local: false,
},
]);
assert.ok(logged.some((message) => message.includes('response too large')));
});
test('a directory with nothing in it is removed and not reported', async () => {
const io = fakeIo({ 'http://bridge/sub/ja': { status: 500 } });
const result = await cacheSubtitleTracks({