fix(server-storage): Stalker keys by category, keepalive ordering, cut-off probe

- Moving Stalker favorites and history keys items by id and category, so
  a channel and a movie with one id both survive.
- Writes after a page-hide flush wait for its keepalive requests.
- A probe whose body is cut off counts as unreachable and keeps the
  remembered server.
- Settings → Server renders without @defer so settings search can reveal
  its rows on the first visit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-12 00:38:18 +02:00
1 parent 6820b87005
commit 676733cd7b
8 files changed
+101 -39

No files matched your search

@@ -78,6 +78,14 @@ describe('probeServerInstance', () => {
for (const fetchFn of [
jest.fn().mockRejectedValue(new TypeError('Failed to fetch')),
answer('Bad Gateway', 502),
// Headers arrived, then the body was cut off.
jest.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => {
throw new DOMException('aborted', 'AbortError');
},
}),
]) {
await expect(
probeServerInstance({ ...config, fetchFn: fetchFn as never })
@@ -64,8 +64,11 @@ export async function probeServerInstance(
return isServerInstance(body) && body.storage === 'server'
? { kind: 'server', instance: body }
: { kind: 'none' };
} catch {
return { kind: 'none' };
} catch (error) {
// Unparseable JSON is an answer; a cut-off body is an outage.
return error instanceof SyntaxError
? { kind: 'none' }
: { kind: 'unreachable' };
}
}
@@ -22,8 +22,8 @@ import type { ServerStorageStatusResponse } from '@iptvnator/shared/interfaces';
/**
* Settings → Server: shown only when the PWA runs against self-hosted
* server storage. Loaded with `@defer`, so other builds never fetch the
* server-storage code for the settings page.
* server storage. Rendered without `@defer` so settings search can reveal
* its rows on the first visit; the code rides in the lazy settings chunk.
*/
@Component({
selector: 'app-settings-server-section',
@@ -120,13 +120,9 @@
/>
}
@case ('server') {
<!-- Deferred: keeps the server-storage code out of
the settings chunk in every other build. -->
@defer {
<app-settings-server-section
(exportData)="exportData()"
/>
}
<app-settings-server-section
(exportData)="exportData()"
/>
}
@case ('backup') {
<!-- Backup & data: the former Reset page is this