Commit Graph
3119 Commits
Author SHA1 Message Date
4grayandClaude Opus 5.5 586ba74ea7 fix(server-storage): adopted writes without a known version conflict instead of overwriting
A page that never read the server has no version tags, so an adopted
write went out unconditionally and replaced newer server data with its
cached value. Adopted edits now use the never-matching version when this
page knows none (and for a queued write without a tag), so the server
answers 412 and the edits are replayed on its value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:37:05 +02:00
4grayandClaude Opus 5.5 2320b64e01 fix(server-storage): adopt a late previous page load instead of restoring it
When touch() finds that the previous page load no longer looks live, this
page may already have read and queued newer edits. Its saved writes and
early edits are now adopted like a closed tab's (their steps ahead of this
page's newer ones, queued at once) instead of restored as if this page
had just started, which replaced newer progress with older and parked
early edits that were never sent after the first read. Cache forgetting
moves into UserDataCache.forget().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:28:00 +02:00
4grayandClaude Opus 5.5 d45210c4f7 fix(server-storage): take over a reload's writes once the previous page load stops looking live
When the close flag could not be saved (storage full), a reload took the
previous page load for a running duplicated tab and left its saved writes
unsent. The page now remembers that page load and its 30 s touch takes it
over as soon as its record is closed, stale or gone, so the session's
retry sends the writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:12:40 +02:00
4grayandClaude Opus 5.5 37c14d7d89 fix(server-storage): keep duplicated tabs off live journals, drain writes around remove-all, normalize legacy Stalker rows on the move
- A page marks its unconfirmed-writes record closed on pagehide (not into
  the back-forward cache); a new page takes over the previous page load's
  saved writes only when that record is closed, stale or missing, so a
  duplicated tab, which copies sessionStorage, leaves the running tab its
  writes.
- PlaylistsService.removeAll lets queued playlist writes land first and
  makes writes issued meanwhile wait until it finished; the server store
  refuses a document without an id.
- Moving browser data sets the explicit Stalker portal-mode flag on rows
  from before it (withExplicitStalkerPortalFlag, now shared with
  PlaylistsService).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:59:18 +02:00
4grayandClaude Opus 5.5 f956758cce fix(server-storage): refuse user state for deleted playlists
PUT /v1/user-state/:namespace/:playlistId answers 404 playlist_missing
when the server has no such playlist, so an edit queued on another device
(including its conflict replay with If-None-Match: *) cannot recreate the
state of a playlist deleted meanwhile. The client drops such an edit and
its cache entry instead of retrying; page-hide writes report it as gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:43:43 +02:00
4grayandClaude Opus 5.5 716580cb16 fix(server-storage): favorite toggles as deltas, early edits as steps on recovered writes, release kept journal copies
- PlaylistEffects.updateFavorites$ applies the toggled channel through
  transformPlaylistFavorites, on the favorites read at write time, instead
  of writing the reducer's whole array: a conflict retry keeps another
  device's favorites (#1919).
- Edits made before the first read on top of this tab's recovered unsent
  write are appended to it as their own steps, so a removal of an item the
  recovered write adds survives a conflict replay.
- Journal copies of earlier page loads kept because this page could not
  save its own are removed once it can.
- adopt/extend move into server-user-data-adopt.ts over a small queue API
  (unsent, isQueued, put); the queue exposes its tag map.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:30:31 +02:00
4grayandClaude Opus 5.5 115465755b fix(server-storage): copy-bound portal versions, stale 401s, unreadable browser maps, early edits on recovered writes
- A served copy carries the version it came from (a symbol property that
  spreads keep and JSON drops), so a mutation of a portal document names
  the version it was built on, not the latest read.
- A 401 for a token replaced since (a new sign-in) neither clears the new
  token nor reports unauthorized, in the API client, the HttpClient
  interceptor and PwaService's proxy calls.
- Moving browser data counts a browser user-data map that cannot be read
  as a failure, so the move stays on offer.
- An edit made before the first read on top of this tab's recovered
  unsent write is applied to that write, not to the server's value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:11:26 +02:00
4grayandClaude Opus 5.5 067190e1df fix(server-storage): conditional writes for every playlist mutation, ordered adoption, keep journals until copied
- Every playlist mutation is sent on the version it was read at: the
  payload's ETag, else the playlist's last read (portal documents have no
  payload). Refused versions are tracked per playlist, so a conflict on
  one never disables the check on another at the same revision. Imports
  and start-up migrations (dbUpsertAppPlaylists) stay unconditional.
- A closed tab's adopted edits replay before this tab's queued ones, from
  the base of its first step, so newer progress is not replaced by older.
- A reload or adoption removes the old page load's saved writes only when
  this page saved its copy; each closed tab is adopted once per page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:31:49 +02:00
4grayandClaude Opus 5.5 7afb9758f4 fix(server-storage): conditional playlist metadata writes, retried on a fresh read
A favorite or rename (keepPayload PUT) is sent with If-Match on the
version it was read at. The server answers 412 when another device wrote
since; the store throws PlaylistWriteConflict and PlaylistsService's
per-playlist write queue runs the mutation again, which reads the
playlist afresh, so two devices' metadata edits both survive. A retry on
the same unread copy writes unconditionally, as before; refresh and
import writes stay last write wins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:17:18 +02:00
4grayandClaude Opus 5.5 2d6f28f8c8 fix(server-storage): retry unsynced user data while online, keep newer typed snapshots, explain a refused token store
- The 30 s retry also re-reads and flushes while online when the first
  user-data read failed or writes the server refused (a 5xx) are queued;
  the same failure notice shows at most every five minutes.
- Moving browser data adds a legacy snapshot alias only where no typed
  snapshot exists, so a newer typed copy is not overwritten.
- writeServerAccessToken reports whether storage kept the token; the
  sign-in page says so (SERVER_STORAGE.SIGN_IN_STORAGE, 19 locales)
  instead of calling a right token rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:00:33 +02:00
4grayandClaude Opus 5.5 70fabb1424 fix(server-storage): apply a caller's edits to the cache as it is now
ServerUserDataStorage remembers the map getItem handed out. When the
cache changed since (another tab saved, a re-read), a write applies only
the entries the caller changed to the current cache, so a map read
before another tab's save no longer reads as deleting that save. The
cache moves into UserDataCache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:45:52 +02:00
4grayandClaude Opus 5.5 2a93100ec4 perf(workspace): style the server banner from the shell, baseline 570
The offline banner's own stylesheet added a <style> element to every
launch, one of the two mutations the launch journey counted on top of
master. Its rules move into the workspace shell's stylesheet.

The other one is a parser-inserted modulepreload link: the new lazy
server-storage chunks split one shared initial chunk in two. A/B on
macOS: master 555, this branch 556 (was 557); the baseline note now
says so instead of blaming @if anchors, which cost nothing here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:45:52 +02:00
4grayandClaude Opus 5.5 8deb194312 fix(server-storage): keep steps of possibly-landed writes apart, queue closed tabs' edits even when the cache shows them
- Steps of a write whose answer was lost, of saved writes taken over after
  a reload, and of a closed tab's saved edits are marked uncertain, so a
  later edit never joins them: adding and then removing an item after a
  lost answer still removes it.
- A closed tab's saved edits are queued ahead of this tab's own even when
  the cache already shows them, so a conflict replays them too.
- The tag map moves into UserStateTags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:16:13 +02:00
4grayandClaude Opus 5.5 72b0d2102a fix(server-storage): keep edits as ordered steps, treat probe refusals as transient, read the cache through memory
- A queued write keeps its edits as steps: an edit continuing the last one
  joins it, one made behind a travelling write or on another tab's change
  stays apart, and a conflict replays them in order. Saved writes and
  early edits store the steps too.
- The instance probe treats any refusal but 404 (a proxy's 429 or 403) as
  unreachable, so a browser keeps its remembered server.
- Cache reads that browser storage refuses fall back to memory, so a
  re-read and later edits still work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:03:41 +02:00
4grayandClaude Opus 5.5 b1b05600d6 fix(server-storage): durable per-page journal, conflicts for edits on other tabs' changes, lock readability
- Unconfirmed writes and early edits are saved in localStorage per page
  load; the tab remembers its page load in sessionStorage, so a reload
  takes them over, and another tab replays a closed tab's writes once its
  record is stale. Live tabs renew their record every 30 s.
- An edit made on another tab's unsent change, and an entry a re-read
  keeps from the cache, are sent on a version no entry has, so the write
  conflicts and is replayed instead of carrying another tab's change past
  a later removal.
- Moving browser data counts the parental-lock step as failed while the
  lock store cannot be read, so the move stays on offer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:49:21 +02:00
4grayandClaude Opus 5.5 487c98ab96 fix(server-storage): replay edits behind travelling writes in order, hold writes after an offline start
- An edit queued behind a travelling write keeps its own before → after
  and carries the travelling write's edit as `prior`, also in the saved
  writes. A conflict replays both in order, so adding and then removing a
  favorite removes it whether or not the first write landed, and a lost
  write is not dropped.
- Page-hide writes now report their new tag or a 412 like normal writes:
  a confirmed one rebases the next edit, a conflict is replayed.
- After an offline start from a remembered instance, writes are held and
  the retry only checks /instance before reloading, so saved writes never
  reach another database behind the same address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:31:45 +02:00
4grayandClaude Opus 5.5 a8b6b86e43 fix(server-storage): keep tags with their values, rebase page-hide writes, hold deletes
- A re-read that keeps an entry from the cache (another tab's or this
  tab's unconfirmed value) no longer takes the server's tag for it, so the
  next edit conflicts and is replayed instead of overwriting another
  device's change.
- Page-hide writes behind a travelling one are taken from the queue when
  it is answered, so its new version becomes their baseline.
- A playlist delete holds that playlist's user-data edits instead of
  discarding them up front; only a successful delete drops them, a failed
  one sends them as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:10:48 +02:00
4grayandClaude Opus 5.5 0183ddc61d docs(server-storage): conditional user-state writes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:58:16 +02:00
4grayandClaude Opus 5.5 340b1c3efa fix(server-storage): conditional user-data writes, scoped token, deletes
- User-state entries carry a content tag; PUT/DELETE accept If-Match or
  If-None-Match: * and answer 412 with the current value and tag. The PWA
  sends each edit on the tag it read and replays it (before → after) on
  the server's value after a conflict, so another device's change to the
  same playlist's favorites or positions is kept instead of overwritten.
- The access token goes only to the backend URL (same origin and path),
  never to another host or a sibling path.
- Deleting a playlist (or all) first drops its queued user-data edits and
  waits for writes in flight, so none recreates deleted data.
- The migration copy writes on the tags it read.
- The write queue moves out of ServerUserDataStorage into
  UserDataWriteQueue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:57:48 +02:00
4grayandClaude Opus 5.5 f49ad59549 fix(server-storage): per-tab saved writes, memory overflow cache, snapshot aliases
- Unconfirmed writes and early edits are kept in the tab's own
  sessionStorage, so tabs never clear each other's; a shared registry of
  unconfirmed entries (one record per tab, ignored after ten minutes)
  keeps a re-read in one tab from replacing another tab's values.
- A cache value too big for localStorage stays readable from memory, so
  later edits are diffed against what the app reads.
- Typing legacy Xtream ids keeps each snapshot's bare key as an alias, so
  a reference not yet rewritten still resolves and a retry can type it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:33:32 +02:00
4gray cedc468325 Merge branch 'master' of https://github.com/4gray/iptvnator into claude/server-storage-pwa
# Conflicts:
#	libs/workspace/shell/feature/src/lib/workspace-shell/workspace-shell.component.ts
2026-10-12 08:19:36 +02:00
4grayandClaude Opus 5.5 2c805e0b61 fix(server-storage): keep early edits across reloads and tabs, numeric-string legacy ids
- Edits made before the first read are saved (before → after) and
  restored after a reload; a second early edit keeps the first one's
  baseline, even an empty one; a cache-full first read still applies them
  to the server's value.
- Queued writes are saved as soon as they are queued, and a re-read keeps
  entries another tab has not confirmed yet.
- Legacy Xtream ids stored as numeric strings are typed too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:17:29 +02:00
6fe684af51 fix(workspace): slide the content and header into place when a source opens (#1895)
* fix(workspace): slide the content and header into place when a source opens

Opening an Xtream portal from the dashboard inserted the 323 px context
panel and the header Back in the frame that swaps the page: the content
pane jumped right by the panel's width and the switcher, search and actions
by Back's slot. That one frame was J2's whole layout shift (0.2327: 0.222
for the content pane, 0.011 for the header, scored with the pane's
distance), plus 0.0001 when the category search and sort buttons appeared
with the categories.

LayoutSlideDirective animates these moves as a 180 ms FLIP: the new layout
applies at once and only a transform animates, so nothing waits and no
layout shift is recorded. The content surface covers the strip it uncovers
while sliding, Back fades in, and reduced motion keeps the jump. The
category search and sort buttons render disabled while the list loads.

Locally J2 renderer.layoutShiftScore reads 0 (was 0.233), J1 0 / 0, J3
0.001, J4 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(journeys): lower J2 layoutShiftScore to 0

The slides land on the runner: run 38147072417 (d2cb9445d) reads
open-source/renderer.layoutShiftScore 0 with no shift recorded in all 5
measured iterations (J1 0 / 0, J3 0.001, J4 0 unchanged). Ratchet the
baseline from 0.233 to 0 and record the perf:compositing evidence for the
slide in the shell contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: 4gray <fourgray@proton.me>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:15:18 +02:00
4grayandClaude Opus 5.5 50fd1c4a63 fix(server-storage): apply early edits to the server value, newest unsent write, sticky sign-out
- Edits made before the first successful user-data read are kept as
  before → after and applied to the server's value once it arrives
  (applyEdit), so a new device or a stale cache adds a favorite instead of
  replacing the server's list.
- Saved unsent writes keep only the newest per entry; an answered older
  write no longer retires a newer one still travelling.
- A late failure after sign-out no longer turns unauthorized into offline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:05:24 +02:00
4grayandClaude Opus 5.5 21f640ccc1 fix(server-storage): locks follow merged playlists, typed legacy Xtream ids, instance-scoped cache
- Moving a playlist under another server id takes its parental locks along
  (they stay in the browser).
- Bare legacy Xtream ids in each copy are typed from that copy's own
  snapshots before favorites, history and snapshots are combined, so a
  live channel and a movie sharing an id both survive.
- Writes still travelling are saved on page hide too; saved writes and the
  cache belong to one server database (cache owner = instanceId).
- A late successful answer no longer turns unauthorized back to online.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:52:35 +02:00
a9f4ae38ea perf(database): rebuild the title index once per profile, not again on the second launch (#1900)
upgradeContentTitleFtsTokenizer recreates and rebuilds content_title_fts and
records migration:content-title-fts-remove-diacritics:v1, but not
migration:content-title-fts-trigram:v1. On a new profile the tokenizer
upgrade therefore did the first rebuild, and on the second launch
ensureContentTitleFts found its own marker missing and rebuilt the whole
index again, synchronously inside initDatabase on the main thread, right
after the first catalog import. A trigram rebuild costs about 4.5 s per
million titles (sqlite3 3.51, synthetic titles). Every J4 iteration paid it
too, since each launches a copy of a freshly seeded profile.

The upgrade's transaction now records both markers. The skipped-release
upgrade harness counts rebuilds across every initialization of a profile
and requires exactly one; it fails for all six cases without the fix.

Co-authored-by: 4gray <fourgray@proton.me>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:37:27 +02:00
5f55553f41 test(e2e): wait for the persisted Xtream import status, not only the overlay (#1897)
* test(e2e): wait for the persisted Xtream import status, not only the overlay

waitForXtreamImportToFinish gave the loading overlay 5 s to appear and then
treated "no overlay" as finished. The overlay appears only once the portal's
account check answers; a freshly started mock generates the large catalog on
that first request, so J4's seed sometimes closed the app while the series
import was still running. The template then had no series rows, and J4's
query for "system" (series titles only) returned an empty list on the first
launch after seeding.

On an Xtream route the wait now also requires the import status the app
persists to read completed for live, movie and series. The new
xtream-import-wait.e2e.ts holds get_account_info past the overlay window and
fails without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(e2e): judge the Xtream catalog complete the way the app does

Review follow-ups on #1897:

- A refresh deletes the catalog and re-imports it without resetting the
  persisted import statuses, so `completed` alone proves nothing after one.
  A type now counts as imported when its status reads completed and it has
  rows, the same rule the app uses before reusing its cache; a type without
  categories (a live-only panel) counts by status, and a portal without any
  category is not imported yet. A second spec refreshes a portal behind the
  slowed account check. Today the refresh keeps its preparation overlay up
  across that check, so it passes with the earlier wait as well; it guards
  the helper's contract.
- The proxy is closed in its own finally, so a failed launch or close no
  longer leaves it listening.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: 4gray <fourgray@proton.me>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:37:22 +02:00
4grayandClaude Opus 5.5 abae207d02 fix(server-storage): unsent writes survive reloads, guarded move, lazy storage
- User-data writes the server has not confirmed are kept under
  iptvnator-server:unsent-writes and sent first on the next visit.
- Moving browser data does not start while user-data writes are unsent,
  and a duplicate of a failed upload counts as failed, not merged.
- The session reaches localStorage through the lazy adapter, so a browser
  that denies storage still boots.
- Raise launch/renderer.domMutationsToFirstCard by 2 (569 → 571): the rail
  server shortcut and the offline banner each leave an @if anchor in every
  build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:36:18 +02:00
4grayandClaude Opus 5.5 72c84a3eba fix(server-storage): keep URL favorites, flush before moving, cache-full and cut-off bodies
- Moving M3U favorites keeps URL favorites and anything the server cannot
  resolve; only older channel ids are translated.
- Queued user-data edits are sent before the move writes combined values.
- A re-read that does not fit the cache keeps the old cache and baseline
  together.
- A response body cut off after its headers counts as offline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:00:07 +02:00
4grayandClaude Opus 5.5 4afd4bcc33 fix(server-storage): cache baseline before first read, versioned retries, M3U favorite ids
- Before the first user-data read finishes, an edit is compared with the
  cache, so it sends only its own playlist entry.
- Queued entries carry their edit number; a failed write is retried only
  when nothing newer replaced it.
- Moving M3U favorites and history translates channel ids through the
  stream URL (ids are random per import).
- The server-storage E2E runs in Chromium only: one shared database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:47:19 +02:00
4grayandClaude Opus 5.5 676733cd7b 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>
2026-10-12 00:38:18 +02:00
4grayandClaude Opus 5.5 6820b87005 fix(server-storage): positions by content, acknowledged keepalive, serial re-reads, token changes
- Moved playback positions are matched by content type and Xtream id and
  name the server playlist.
- Page-hide writes report whether the server took them; refused ones go
  back into the queue. Re-reads run one at a time.
- The access guard's 401 carries WWW-Authenticate: Bearer
  realm="iptvnator"; the interceptor and PwaService's raw fetches turn it
  into sign-in, so a token changed on the server is noticed on provider
  calls too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:29:55 +02:00
4grayandClaude Opus 5.5 fc32934ce3 fix(server-storage): merge user data on move, keep failed writes, sign-in route gate
- A merged playlist gains the browser document's favorites and history;
  Xtream values for one server entry are combined (lists unioned, maps
  merged, the server winning conflicts) and written once.
- A failed user-data write stays queued and survives a re-read; the
  page-hide flush waits for a batch still travelling.
- The start-up screen lets the sign-in route through whatever its check
  reports.
- The backend refuses IPTVNATOR_ACCESS_TOKEN without server storage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:56 +02:00
4grayandClaude Opus 5.5 676e3adcd4 fix(server-storage): keep Xtream data, ordered sync, sign-in and stale-cache fixes
- Moving browser playlists matches them to the server's by id or source and
  carries the legacy Xtream maps under the server id; it no longer goes
  through the backup format, whose PWA export has no Xtream user data. A
  partial move stays on offer.
- User-data batches go out in order, and a re-read keeps entries edited
  while it was in flight.
- A keepPayload write keeps its cached copy only when the server replaced
  the version its payload came from (new X-Replaced-ETag header).
- 502/503/504 from the proxy count as offline.
- The start-up screen lets the sign-in page through while a token is
  required.
- Settings → Server offers a reload after a move.
- The probe detects Electron through RuntimeCapabilitiesService.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:56 +02:00
4grayandClaude Opus 5.5 3bfd5394ef fix(pwa): stay in server mode when a known server does not answer
A failed start-up probe used to fall back to browser mode, which showed an
empty library while the backend restarted and let new playlists land in the
browser. The browser now remembers the server; without an answer it starts
in server mode, offline, and reloads once the server is back. Settings →
Server shows the backend address instead of the page's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:56 +02:00
4grayandClaude Opus 5.5 8714b3b445 docs(self-hosted): server storage mode, sign-in and the release note
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:56 +02:00
4grayandClaude Opus 5.5 ced58e3ac0 test(e2e): server storage across browsers and the migration dialog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 77df7922c1 feat(i18n): server storage strings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 932971d498 feat(settings): warn that delete-all clears the server
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 fa79c3eb6d feat(workspace): server status in the rail and an offline banner
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 07b942a832 feat(settings): server section for self-hosted storage
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 cec1ad83a2 feat(pwa): server sign-in page and migration dialog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 c5477c5f64 feat(pwa): probe for server storage and wire server mode
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 57f2a33396 feat(services): move browser playlists to the server
An empty server gets the browser playlists with their ids plus the
legacy Xtream user-data maps; a server that already has playlists goes
through the backup export and import, so duplicates merge. Browser data
is never deleted; the decision is remembered per server instance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 fadb6e5ae2 feat(services): server storage session and token interceptor
The session plugs the HTTP document store into PlaylistStorageBackend,
hydrates synced user data before the first read (bounded to 3 s), retries
while offline, flushes on hide, and routes to sign-in when the server
answers 401. The interceptor adds the access token to HttpClient calls to
the backend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 533222d137 feat(services): server-synced user data storage
Synced Xtream maps live in a write-through cache under the
iptvnator-server: prefix; writes send only the playlist entries that
changed, debounced, and flush with keepalive when the page hides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 ead47697bd feat(services): playlist document store over the server API
Full documents are cached by ETag (weak tags included) and served as
private copies; an upsert whose payload is the one the store handed out
sends keepPayload=1 without it and retries in full on payload_missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 b74584fdb2 feat(services): server storage API client
The @iptvnator/services/server-storage sub-entry keeps server mode out of
the eager services barrel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 ab2eef26d2 feat(services): server kind for the playlist storage backend
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00
4grayandClaude Opus 5.5 872d8edc68 feat(services): server storage connection state and access token
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:16:55 +02:00