mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 14:16:16 -08:00
aabca1fae14cc3c91c3e82a9caa626495dcf2f91
82
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
aecb9acb8e |
feat(workspace): route platform Back gestures to the header Back (#1943)
* feat(workspace): route platform Back gestures to the header Back The mouse Back button, Alt+Left (Cmd+[ on macOS) and, in Electron, the window's app-command browser-backward (Windows/Linux) and trackpad swipe (macOS) now run the workspace header's Back, one step each. - WorkspaceBackGesturesService (shell-provided) dispatches every gesture through WorkspaceBackNavigationService.goBack(). A gesture is swallowed while a dialog, menu, the phone drawer or a fullscreen player is up, and left to the browser only where the header has no Back. - The chord is a navigate-back entry in the shortcuts dialog, handled in a capture-phase listener of WorkspaceKeyboardShortcutsService; ignored while typing, repeats swallowed. - Electron forwards app-command and swipe over the push-only channel WINDOW:BACK_REQUESTED (preload onWindowBackRequested), gated in the renderer on RuntimeCapabilitiesService.supportsWindowBackGestures. - A mouse press that also arrives as app-command goes back once, however long the button is held; Electron's own Chromium history step on the mouse button is always prevented. - No Forward binding. Docs: workspace-shell (Platform gestures), portal-detail-navigation, electron-security, UI guidelines, README shortcuts table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(workspace): drop NgZone from the Back gestures service Master runs zoneless change detection and no longer uses NgZone; the bridge callback calls goBack() directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(workspace): keep Back gestures where the Fullscreen API is missing iPhone Safari has no unprefixed document.fullscreenElement; the undefined value read as "a player is fullscreen" and blocked every gesture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(workspace): keep a held Back chord's repeats swallowed after Back Repeats asked for the current result, so once the first press reached a page with no Back left they were released to the browser, which could take another history step. Repeats now follow the chord's first press until a key is released. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(electron): one Back per press of the Linux Browser Back key On Linux Electron raises app-command browser-backward for every unhandled event of the Browser Back key (NativeWindowViews:: HandleKeyboardEvent), keydown and keyup alike, so one press went back twice. The main process now consumes that key in before-input-event and requests Back once per press; mouse Back buttons still arrive as app-command. 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> |
||
|
|
8098ea8bb8 |
feat(self-hosted): keep playlists on the server for every device (#1903)
* feat(services): server storage connection state and access token Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(services): server kind for the playlist storage backend Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 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> * 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> * 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> * 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> * 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> * feat(pwa): probe for server storage and wire server mode Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(pwa): server sign-in page and migration dialog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(settings): server section for self-hosted storage Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(workspace): server status in the rail and an offline banner Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(settings): warn that delete-all clears the server Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(i18n): server storage strings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(e2e): server storage across browsers and the migration dialog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(self-hosted): server storage mode, sign-in and the release note Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * docs(server-storage): conditional user-state writes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * 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> * fix(server-storage): a re-read sent before a playlist delete skips that playlist ServerUserDataStorage records each successful delete; a re-read sent before it drops the deleted playlist's entries and tags from the server's answer (withoutDeleted), so a late answer cannot write the deleted favorites and positions back into the cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): keep a playlist id like __proto__ when filtering a read withoutDeleted() builds its maps without a prototype, so a surviving playlist whose id is __proto__ stays a key instead of changing the object's prototype and losing its favorites and tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): reorders on the stored favorites, URL-insensitive remembered server, prototype-free id maps - PlaylistEffects.setFavorites$ applies the drag's order through transformPlaylistFavorites (reorderFavorites): favorites added on another device stay, ones removed there stay out. - The remembered server matches the backend URL by origin and path without trailing slashes, so /api and /api/ start offline in server mode alike. - Maps keyed by playlist id are built without a prototype (idMap), so an id such as __proto__ is an ordinary key in the cache, conflict replays, re-reads and tags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): one registry key per page load, server token only in server mode - The unconfirmed-writes registry stores each page load's record under its own key (iptvnator-server:unconfirmed:<instance>:<page>), listed through a new keys() on the user-data storage adapter, so tabs never overwrite each other's records. Saved writes without a fresh record, also when the record was lost, are adopted as a closed tab's (#1920). - PwaService sends the stored server token on its raw fetch calls only while server mode runs, and a backend that answers without server storage makes the browser forget the token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): forget the token for another server, listen before sign-in, adopt only stale records - A server that answers as another backend URL or instance id than the remembered one makes the browser forget the stored token, so server A's token never reaches server B. - The session installs its page-hide and heartbeat listeners before it returns for sign-in, so a page holding another page load's writes still marks itself closed on unload. - Closed tabs are found by stale records only again: saved writes without a record may belong to a running tab whose record storage refused, and are left to that tab's own reload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): no token for an unverified instance, sign-in remembers its server - ServerStorageStatusService.accessToken() returns the stored token only while server mode runs on an instance this start verified. The API client, the HttpClient interceptor and PwaService's raw fetch calls use it, so after an offline start from a remembered instance no request (including the /instance reachability check) carries the token to whatever answers at that address, and a 401 then does not clear it. - Sign-in remembers the server along with the token; when storage refuses either, it reports SIGN_IN_STORAGE instead of reloading into a start that would drop the token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): check a remembered instance before sign-in sends the token A page started from memory (startedOffline) withholds the token until the instance is verified, so sign-in on that page sent no token and the server's 401 cleared the right one. Sign-in now first calls ServerStorageSession.ensureVerified(): it reads /instance without a token, marks the status verified when the instance id matches, and reloads the page when another server answers at the address. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): read migration maps by own playlist ids only The browser's legacy maps and the server's user-state entries are read into null-prototype maps, so a playlist id such as `toString` is not taken for an entry and a missing server value no longer resolves to an inherited function. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): bind the token to its instance; adopted edits yield to newer changes The access token is stored with the instance id it was given for, and a page reads it only for its own instance: a tab still open on the old backend after BACKEND_URL changed no longer sends the new server's token there. The API client hands every 401 to reportAccessDenied(), which now also sends a page without a token for its instance to sign-in, while one that holds its token back until the instance is checked keeps it. A closed tab's adopted edits carry `yields`: replayed, they change only what is still as that tab saw it, so they never replace progress this tab or another device saved since. The flag survives the journal. The sync spec's fake server and memory storage move to a test-helpers file to keep the spec under its line limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): per-instance cache, idle tabs adopt closed ones, backups wait for the first read The user-data cache is keyed by server instance (`iptvnator-server:cache:<instanceId>:<namespace>`): a tab still open on the old backend after BACKEND_URL changed no longer writes over the new server's cache, which the new tab's next edit would have sent there. claimCache also removes the old owner's keys and the unscoped keys of earlier builds. touch(), which runs every 30 s, adopts closed tabs' saved writes once this tab has read: a tab with nothing to sync never re-reads, so they waited until a tab switch or reload. UserDataStorage gains an optional ready(); the server storage resolves it after its first read and rejects while that fails. A PWA backup export awaits it, so a fresh browser whose /user-state read is slow or failed cannot export empty favorites, history and positions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): token only to backend URLs in raw fetches; Stalker series keys in the move PwaService joins paths to the backend URL without its trailing slashes, so BACKEND_URL=/ no longer yields a protocol-relative //… URL that names another host, and it attaches the server token only when the request URL resolves to the backend (isBackendUrl), like the interceptor. The move's item key adds the Stalker series flag to id and category: a provider category can hold a movie and a series of one id, and the series favorite was dropped as a duplicate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): Xtream reads and backups wait for the first read; keepalive deadline Startup waits for the first user-data read only a few seconds. ready() now waits for a first read in flight without starting one (an offline page is not held up) and tells whether one succeeded: - PwaXtreamDataSource awaits it before reading favorites, recent items and playback positions, so a new device whose /user-state read is slow does not show empty lists or start playback without its resume point. - A PWA backup export and restore fail while no read succeeded; the restore stays parked, since its replacement lists would otherwise merge into the server's (an empty favorites list changed nothing). A page-hide keepalive write gets the request timeout: a server that takes it and never answers no longer holds every later write, re-read and delete behind it until reload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): a failed sign-in keeps another tab's token; moved history newest first A sign-in that fails clears the token only while it is still the one this attempt stored: another tab that signed in meanwhile keeps its valid token. The move orders the combined Xtream recent items by viewedAt, newest first, as the app keeps them: browser views appended after the server's history were cut by the next view once the server held 50. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): recheck the instance after offline, back-forward cache pages, Stalker server rows Going offline now marks the instance unchecked: the token and writes wait until /instance answers with this page's instance (another one reloads the page). A replaced volume at the same address no longer gets the old token and queued user-state writes on reconnect. The session checks as soon as it goes offline, so a blip costs no retry interval. A page restored from the back-forward cache whose record another tab adopted meanwhile (its timers were frozen, so the record went stale) reloads instead of sending those writes again after newer edits. The Stalker playlist metadata migration also runs on server rows, once per server: a desktop database copied to the server can predate it, and its Stalker rows lacked isFullStalkerPortal. The take-over and change-diff loops move out of the user-data facade, and the adoption specs get their own file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): adopted edits keep yielding through conflicts; newest view per item; same-token sign-ins A conflict replay collapsed every step into one edit, so a closed tab's adopted step lost `yields` after the first conflict and a second one replayed its old position over a newer one. Its steps now stay as they are, and only this page's own collapse into one edit on top of them. The move combines recently viewed items one per item, keeping the newest view and its artwork, then newest first; before, the server's older copy of an item won over a later browser view. A failed sign-in clears the stored token only on a 401: a network failure says nothing about the token, and another tab may have stored the same one meanwhile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): suspended tabs give up adopted writes; history stays newest first; favorite checks wait A background tab the browser suspended past the record lifetime can have its writes adopted elsewhere, like a back-forward cache page: shown again, it now checks resumed() and reloads instead of sending them. Conflict replays appended a newly viewed item at the end of the history, where the next 50-item cut dropped it; a replayed recently viewed list is now kept newest first. isFavorite() waits for the first user-data read like the list reads, so a detail page opened during a slow first read does not toggle an existing favorite the wrong way. A parked backup restore replayed when the playlist opens waits too, and stays parked while no read succeeded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): offline recheck checks only the instance; sign-out always navigates The recheck on going offline ran a full refresh: when /instance answered but /user-state kept failing, each failed read set the page offline again and started the next one at once, bypassing the retry interval. It now checks the instance only; reads wait for the retry. Sign-out navigates to sign-in itself, also when the page is signed out already: a guard (unsaved Settings) may have cancelled the redirect the first time, and the state change that drives it does not repeat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): a superseded page sends and saves nothing; re-reads keep other tabs' newer saves A suspended tab's retry timer could run before it was shown again: touch() recreated the record another tab had removed when it adopted the page's writes, so the page sent them again over newer edits. The facade now treats a page whose record is gone as superseded: the queue sends nothing (maySend hook), its journal and record are not written again, and the retry timer reloads it like a page shown again. A re-read answered after another tab saved an entry no longer writes the older answer over the shared cache: entries changed while the read travelled are kept, with an unknown tag, so the next write here conflicts and replays instead of removing what the other tab saved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(web-e2e): never reuse another backend as the storage E2E backend server-storage.e2e.ts deletes every playlist before each test. Reusing a backend already listening on STORAGE_BACKEND_PORT outside CI could have wiped a real library; an occupied port now fails the run instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): an outage on the sign-in page also unverifies the instance reportOffline() left `verified` set while the page was unauthorized, so a sign-in retried after an outage skipped the instance check and could send the old instance's token to a replacement database. Verification is now cleared on any outage; only the state change stays guarded, so the workspace remains locked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): interceptor and raw fetches report backend outages too Only the storage API client reported an unreachable backend as offline. When the first sign of an outage was an HttpClient call through the interceptor (provider targets, playlist parsing) or one of PwaService's raw fetches (Stalker, connectivity reset), the instance stayed verified, and the next request after a replacement instance came up at the same address would carry the old token. Both now report no answer (status 0, a failed fetch) and a gateway's 502/503/504 as offline, so the session checks /instance first. The backend's own JSON 502 for a failed provider is an answer and changes nothing; neither does a fetch this page aborted on its own timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): the immediate offline check runs once per retry interval A response whose headers arrived but whose body was cut off reports success and then offline, which started the next immediate instance check at once: repeated cut-off answers kept requests going and the connection indicator flickering. The check on going offline now runs at most once per retry interval; later failures wait for the timed retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): a saved write without a readable tag counts as saved; unverified online pages keep retrying A write whose headers said saved but whose body (with the new tag) was cut off was retired without confirmation: a newer queued edit kept its steps and old tag, and a later conflict replayed the saved step over a newer value from another device. It now counts as saved on an unknown version: the landed steps drop out and the next write conflicts, so only edits made after it replay. Writes that were unconditional stay so. The keepalive path does the same. A late answer to a request sent before an outage can set an unverified page online; with nothing to sync the retry then never ran and the token stayed withheld. The retry now also runs for online pages that are not verified. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): keep server storage off the startup bundle; form-field defaults on sign-in PwaService imported isBackendUrl from the @iptvnator/services/server-storage barrel, which pulled the whole storage subsystem (sync, migration, API client) into main, though server mode loads it by dynamic import. The helper now lives with the token helpers in @iptvnator/services (re-exported from the interceptor): renderer.initialBytes 1,392,902 -> 1,357,795 bytes locally. The remaining 11,988 bytes over #1931's baseline are what must run before the first route (instance probe, connection status, token bound to its instance, storage backend and write serialization); the baseline is raised with a note, under the PR's perf-baseline-increase label. The sign-in component provides the form-field defaults per component, as master's check (#1928) now requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): conflict-safe Stalker metadata migration on the server; newest document views in the move The Stalker metadata migration wrote server rows back through the unconditional bulk upsert, from the list read: a device that edited a Stalker playlist in between (or a second browser migrating the same copied database) lost its change. Server rows are now read again one by one and written as conditional mutations on that version; a conflict leaves the flag unset, so the next start retries. Moving a document's history kept the server's copy of an item both held, even when the browser had viewed it later, and skipped the write when the length did not change. The combined history is now newest first by added_at, one entry per item, and any content change is written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): M3U favorites by URL and history channel ids in the move; restores wait for the server The move's M3U remap swapped references for server channel ids, but the app looks favorites up by stream URL and history entries carry the URL as their id: legacy favorites became invisible and history duplicated. Legacy id favorites now become their channel's URL on both sides, and history keeps its URL id with the server's channel id as channel_id. A restore in server mode reported success once its lists were queued: a list the server refused (413) left the old data while the parked restore was consumed. Both restore paths now wait for that playlist's writes (UserDataStorage.flushed, a few flush rounds for conflict replays) and stay parked when the server did not take them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): a closed page record stays closed when a late write rewrites it On pagehide the page flushes with keepalive and then marks its record closed. When writes were held (offline), the keepalive write fails at once and its requeue saved the journal again a microtask later, which rewrote the record without `closed`: the reload took the previous page load for live and left its favorites and progress unsent until the record aged out (ten minutes). markUnconfirmed now keeps an existing record's closed flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): the move uploads playlists create-only and merges into a copy moved first Two tabs or devices moving browser copies with the same playlist id both planned an upload; the browser documents carry no served version, so both PUTs were unconditional and the later one overwrote the first copy (and any change made on it since), with both moves reporting success. The backend's playlist PUT now honours `If-None-Match: *` (412 when the playlist exists; checked and written in one synchronous handler). The move uploads with it through ServerPlaylistDocumentStore .dbCreateAppPlaylist, and an upload that finds its id taken merges its favorites and history into that copy instead, counted as merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): Delete All waits for repairs holding a reservation; backups read the server again Delete All raised its write barrier before it held the exclusive authority reset. A Stalker repair already holding its row reservation then waited on that barrier for its conditional write, while the reset waited for the reservation: neither finished, and later playlist operations stalled behind them. The barrier now goes up inside the reset, once repairs in flight have finished; writes queued so far land first and new ones wait for the delete, as before. ready() said yes after any earlier read, so an export could miss what another device added since, and restoring an empty list did nothing when the cache was already empty. Backup export, restore and the restore replay now ask for a fresh read (ready(true)) and fail, the restore staying parked, when it does not succeed. The facade's adoption loop moves to the adopt module to keep the facade under its line limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(services): Delete All drains queued writes before its reset; reserved writes skip its barrier The previous commit raised Delete All's barrier inside the exclusive authority reset and drained queued writes there, but a queued deletion or import takes the shared side of that lock: the reset waited for it while it waited for the reset. Delete All drains the writes queued before it outside the lock again, then takes the reset, with its barrier up from the start. The deadlock with a Stalker repair (or Edit) that holds a row reservation and writes under it is broken on the other side: such a write does not wait for the barrier (holdsPlaylistAuthority, counted per reservation in this context), and the reset waits for the reservation as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): a re-read keeps writes another tab had unconfirmed when it went out A re-read checked other tabs' unconfirmed entries only after its answer arrived. A tab whose write was unconfirmed when the read went out (its edit already in the snapshot) and confirmed before the old answer came back matched neither check, so the stale answer replaced its saved value in the shared cache; that tab's next edit, holding the newer tag, then wrote the stale list and deleted the item on the server. The entries unconfirmed when the read is sent are kept too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(services): writes wait for Delete All before they queue; restores replace only settled lists Delete All, third variant: a write that arrived after Delete All waited for it inside its playlist's queue. A Stalker repair that reserved that playlist meanwhile skipped the barrier, but its write still queued behind that waiting one, while the reset waited for the repair's reservation. While a Delete All runs, writes now wait for it before they join their queue, so no queue holds work blocked on the reset; a write under a reservation held here joins at once. A restore replaced lists while this page's own write for the playlist was still unsent (refused, or lost): that write's edits joined the replacement and a conflict replay kept items the backup did not have. Both restore paths now require the playlist's writes to be on the server first (flushed), and otherwise stay parked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): a reload finds page loads a reload before it kept; restore master's tick baselines When storage refused a reload's copy of the last page load's saved writes, the old copy stayed but only this page's memory named it: the tab's pointer already named the new page, so a second reload before any copy was saved recovered from the empty new page, and without a registry record no other tab could find the old one. The pointer now also lists the page loads a page keeps (keepPages), and a reload takes them over, oldest first, before its last page load. The pointer helpers move to server-page-pointer.ts. Both merges of master resolved journey-baselines.json by taking this branch's whole file, which dropped #1929's cdTicksToFirstCard, cdTicksIdle30s and cdTicksToFirstPage. The file is master's again with only this branch's initialBytes and domMutationsToFirstCard raises. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): a duplicate leaves a running tab's kept page loads to it A duplicated tab copies the tab's session storage, so after a reload that kept older page loads its pointer lists them too, and the duplicate took them over at once, although the tab still running owns those edits and may have newer ones queued; its replay could then write older watch progress over newer. When the previous page load still runs, the kept ones now wait with it and `touch` adopts them, oldest first, once it no longer looks live. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): a restore merged by a conflict stays parked; first-read tests wait for the read A restore whose write conflicted with an edit another device made during it had the conflict replay merge that edit in, and flushed() still said the lists were taken, so the restore was consumed with lists that were not the backup's. Both restore paths now compare the stored lists with the ones they wrote once the server took them, and a difference keeps the restore parked to run again on a fresh read. The two first-read tests released the mocked read after a fixed number of microtask turns, which could come before the read started; they now wait until it has. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): page-hide keepalive writes report an outage too sendKeepalive() turned a failed fetch, a timeout or a gateway's 502/503/504 into a failed outcome without telling the status, so the instance stayed verified: when the hidden tab came back, its refresh skipped the /instance check and resent the queued writes, which could reach a replacement database behind the same address. Like requests, a keepalive write now reports these as offline, so the instance is checked before anything goes out again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): a keepalive answer cut off after its headers counts as offline A page-hide write whose headers said saved but whose body broke off was returned as saved with the read error dropped, so the instance stayed verified. Like readJson() for requests, it now reports offline (the outcome stays saved on an unknown version), and the instance is checked before the next token or write goes out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): kept page loads replay oldest first; restore checks compare normalized reads A page load and an older one it kept could both hold steps for one entry. Adopting them, each adopted page went ahead of what was queued, so adopting the oldest first put the newer page's steps before the older's; restoring them after a reload collapsed the newer page's steps into one, so its removal of an item the older page had added was lost on a conflict replay. Adoption now goes newest first, and a reload appends a later page's steps (and early edits) after an older copy's. restoreUserData() compared the lists it wrote with reads that drop duplicate favorites, so a saved restore listing one twice never finished, also in browser-only mode. It now compares reads right after writing with reads after the save. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): flushes wait for a page-hide batch sent at once; keepalive 401s; parked restores move A flush scheduled in the same turn as a page-hide flush was chained on the queue before the keepalive batch, which went out at once beside it: a later edit (removing a favorite the keepalive batch added) could land first and be undone when the keepalive write arrived. A scheduled flush now waits for a page-hide batch sent at once before it takes entries. A keepalive write refused with 401 returned failed without telling the status; like requests it now reports the token it sent as refused. The move copies a parked Xtream restore (a browser backup import that could not finish) to the server playlist id a playlist merged into, so it is applied and exported there; one parked there already wins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(server-storage): a slow server does not reload a page started from memory forever The start probe gives /v1/instance 2.5 s. A remembered server slower than that started the page from memory; the recovery check (with the request timeout) then got an answer and reloaded, and the reloaded page timed out again: an endless reload loop for a server answering in 2.5 to 20 s. The reload now leaves a one-time mark in sessionStorage, and the next probe waits as long as requests do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e29f36426b |
perf(tooling): compositing probe, report command and route-wide clip-mask guard (#1858)
* perf(tooling): compositing probe, report command and route-wide clip-mask guard Make the instrument that found the dashboard's clip masks part of the repository: `src/performance/compositing-probe.ts` reads the layer tree over CDP (bounds in device pixels, compositing reasons and owner nodes, waiting for Blink's layer debug info), classifies synthesized clip masks (no owner node and no compositing reason) and reads the renderer's `cc/tile_memory` from a memory-infra dump. `pnpm run perf:compositing` seeds one profile against the Xtream mock on a dedicated port, serves the artwork from memory, fixes the window at 1600x1000 and records tile and image memory, layer counts, the largest layers, masks and tile warnings for Live TV, the movie and series details, the dashboard (idle, crossfade, scrolled) and settings into dist/performance/compositing/<timestamp>/summary.json and a console table. Both helpers have node:test coverage in the performance harness. The dashboard-only compositing E2E becomes `compositing.e2e.ts` and guards Live TV, a movie detail, a series detail and the dashboard in one launch, with the backdrop-filtered controls confirmed as composited layers on the dashboard and the movie detail. The rule now lives where it triggers: a Rendering Cost section in the theme/style skill, a validation line in the UI design skill, a context map row, and a Compositing budget section in the performance journeys contract (budget, mask mechanism, instruments, why megabytes are evidence rather than a ratchet). Validation map and README mention the command. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * perf(tooling): sample the dashboard idle reading with rotation paused The hero advances after 8 s, so a 9 s wait sampled the next slide one second into its zoom (backdrop scale 1.116 instead of 1.1, the previous slide's tiles still pooled): 224 MB read as idle, 122 MB with rotation paused and the zoom and tile pool settled. The warning delta and its checkpoint are now taken at one boundary after the sampling, so a "tile memory limits exceeded" line logged while the layers or the dump are read counts for that route instead of for none. The report's test artifacts go under dist/test-results like the other configs'. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * perf(tooling): retry a memory dump that carries no renderer tile memory One reading of seven came back without `cc/tile_memory`: the dump's trace events reach the buffer shortly after the request resolves, and a process can skip a dump it is busy for. The capture now waits briefly before ending the trace and retries up to three times before a reading stays null. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * perf(tooling): run the compositing report with motion enabled With the OS set to reduced motion the hero neither rotates nor renders its pause button, so the report's click timed out before the summary was written. The report now emulates `prefers-reduced-motion: no-preference`, which also keeps the idle and crossfade readings on the same animation set on every machine. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * perf(tooling): sample the crossfade frozen, count split stderr lines, drop stale layer samples Review findings on the pushed head, plus the CI failure: - A layer that vanished between the LayerTree snapshot and its reasons query was kept with empty reasons; without an owner node it would pass for a synthesized mask. Such a snapshot is now discarded and retaken. - The crossfade reading sampled layers, owners and memory across the 700 ms fade, so the row depended on how long the sampling took. The page's animations are frozen 300 ms into the fade while the reading is taken, resumed afterwards, and the incoming slide's zoom is allowed to finish before the scrolled reading. - A stderr chunk can end mid-line; the warning counter now keeps the unfinished line for the next chunk. - The mock server's launch spec lists the new Playwright config. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
6e229f85b6 |
test(perf): add the J3 playback journey (#1774)
* test(perf): add the J3 playback journey J3 clicks a live channel of an Xtream portal and ends at the built-in HTML5 player's first `playing` event, with `loadedmetadata` as a secondary phase. It follows J2: every iteration is a fresh J1 launch on a copy of a profile seeded through the app's dialogs, and the click happens after the app has settled in the portal's first live category. The portal is the mock's `live-fallback` account, whose `.ts` live URLs serve the local H.264/AAC MPEG-TS fixture that mpegts.js plays through MSE on every platform. The marketing accounts' local live bytes are zero-filled and never reach `playing`. Seeding selects the HTML5 player and the `ts` stream format; the catalog's picsum.photos logos are cancelled from the test side so no request leaves the machine. Counters: renderer.ipcCallsToPlaying, renderer.httpRequestsToPlaying, renderer.domMutationsToPlaying, renderer.layoutShiftScore and renderer.longTasks; wall-clock click->loadedmetadata and click->playing. renderer.ipcSerialDepthToPlaying is listed as unavailable until the serial-depth helper lands. No baseline yet. The probe gains a media-event terminal; J2's pre-click settle moves to journey-click-settle.ts so both journeys share it unchanged, and the probe spec's jsdom fixtures move to a shared test helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(perf): show J3's HTTP boundary margins and watch 1 s after playing Review follow-up. renderer.httpRequestsToPlaying compares the ledger's arrival stamps with the renderer's click and playing stamps, which come from different processes on the same host clock. Each iteration now records the distance of the nearest request on either side of both boundaries, so a count a clock difference could flip is visible. Requests after playing were a single snapshot taken right after the probe; the test now watches the ledger for a fixed 1 s after playing. A live stream never leaves the mock quiet, so J2's quiet wait does not apply. 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> |
||
|
|
ea51cae3db |
feat(settings): search settings from the header and the command palette (#1714)
* feat(settings): search settings from the header and the command palette The header search on the Settings page was shown but disabled. It now searches a shared index of all 56 settings rows by translated title, description and English synonyms, replaces the section page with ranked results, and opens a result by scrolling to, focusing and briefly highlighting its row. Enter opens the best match, and the section navigation shows per-section match counts. The command palette gains a "Settings" group that lists the best six matches for a non-empty query, so any setting is one Ctrl/Cmd+K away. Rows hidden by the current form state fall back to the control that reveals them; rows the runtime cannot render are never returned. The index ships through a new @iptvnator/workspace/shell/util/settings-search sub-entrypoint so it stays out of the eager bundle, and a registry spec keeps it in step with the section templates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(settings): let search reveals win over pending input and gate embedded MPV rows - A reveal (result click, command palette, Enter) now cancels a search keystroke still waiting for its debounce, so its q navigation can no longer supersede the reveal and leave the results open. - Embedded MPV extra options and auto-reconnect require a lazily probed embedded MPV capability; frame copy also needs frameCopyAvailable, so search never offers a row the settings page cannot render. - Keyboard users keep a focus-visible ring on the revealed row after the highlight fades. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(workspace): wait for palette probes without Promise.allSettled The web tsconfig lib predates Promise.allSettled; use Promise.all over rejection-safe probes instead. 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> |
||
|
|
0f1cad4b16 |
test(performance): add the J1 launch-to-usable journey benchmark (#1698)
* test(performance): add the J1 launch-to-usable journey benchmark Implements plan items A1, A3 (J1 only) and the minimal A4 from .plans/2026-09-25-performance-journeys-ratchet.md. - journey-renderer-probe.ts: init-script probe counting DOM mutations, layout shifts and long tasks until the first source card is visible on /workspace with the splash removed; unit-tested with jsdom fixtures. - journey-main-ipc-capture.ts: counts bridge invocations from the preload's renderer-API trace channel up to a sentinel call the probe fires, so the IPC counter is exact without touching production code. - launch.journey.ts + playwright.journeys.config.ts: seeded profile (one M3U source, one Xtream portal on the loopback mock), one warm-up and five measured iterations, fresh process and data directory each, writing dist/performance/journeys/<timestamp>/summary.json with exact counters and P50/P90 wall-clock. - Nx target electron-backend-e2e:journeys and root script perf:journeys. - docs/architecture/performance-journeys.md, README, context and validation map entries. renderer.cdTicksToFirstCard and main.sqlStatementsBeforeReadyToShow are reported as unavailable with the reason instead of being faked. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(performance): gate the renderer load so the probes never race startup Review follow-up for #1698. - journey-renderer-gate.cjs: a main-process hook loaded with `-r` (the mechanism Playwright uses for its own loader) makes the first loadFile/loadURL navigate to about:blank and holds the real load until the test releases it. Playwright reports no page before a navigation commits, so this is what lets the renderer probe be registered on the page before the real document exists; the IPC capture is installed before the release too. A safety timeout releases the gate on its own and marks the iteration invalid. Unit-tested with a fake BrowserWindow. - launch-journey-app.ts: registers the probe on the parked page, releases the gate, waits for the real document to commit, fails fast when the probe is missing, and refuses an iteration whose gate timed out, saw a second load, or released before the probe was in place. - journey-renderer-probe.ts: entries delivered live after the terminal batch are buffered and filtered by the same cutoff as queued ones, and the cutoff is sampled in a timer queued from the first rAF, i.e. after the card's frame is painted, so the render task's long task and layout shift are consistently included. - launch-journey-record.ts: layoutShiftScore rounded to three decimals; a 0.0001 shift flipped in and out of the cutoff between iterations. - playwright.journeys.config.ts: reuse a mock server left on the journeys port locally (its fixtures are deterministic); CI still starts its own. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(performance): validate the journey gate after the probe completes Codex follow-up on #1698: the gate state returned by release() cannot see a reload or recovery navigation that happens before the first card. Re-read the live state once the renderer probe has finished and validate that instead, so an iteration spanning an extra navigation is rejected. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(performance): fail an iteration whose performance observers were unavailable Codex follow-up on #1698: a renderer that cannot observe layout-shift or longtask entries used to pass the probe with zero counters, which a ratchet could not tell apart from a genuine zero. The probe assertion now rejects such an iteration and names the missing observer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
d3b6e548cc |
ci(performance): fail when the web app's initial bytes grow (#1694)
Third step of the performance-journeys ratchet, stacked on #1693 (which is stacked on #1692; merge in order, GitHub retargets each to `master`). - New `Initial bytes ratchet` job in `.github/workflows/ci.yml` (ubuntu-latest): install, `pnpm nx build web --skip-nx-cache` (production configuration, the one users download), then `pnpm run perf:initial-bytes:check`. The job fails when `renderer.initialBytes` exceeds `tools/performance/journey-baselines.json`. - `dist/performance/` is uploaded as the `performance-journey-summary` artifact on every run, so a failing or tightenable run carries its evidence. - After review: the job first runs the new `tools/performance/check-baseline-direction.mjs`, which compares `journey-baselines.json` with the revision the change is measured against (the target branch of a pull request, `github.event.before` for a `master` push, `master` for a manual dispatch) and fails on any raised enforced limit (`value × toleranceRatio`), any widened or newly added tolerance, or any removed entry, so a PR cannot grow the payload and raise the baseline to match (lowered limits and new entries pass; a target branch without the file has nothing to weaken). Node tests cover it. - Docs: the performance-journeys contract and the validation map name the job, and the contract now states that this runner is the canonical measurer (take baseline values from its output, not from a local build). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
8bc877b625 |
chore(performance): measure initial bytes of the built web app (#1692)
First step of the performance-journeys ratchet (plan thread: J1 `launch`, counter `renderer.initialBytes`).
- `tools/performance/measure-initial-bytes.mjs` reads the built `dist/apps/web/index.html` and sums `index.html` plus every same-origin `<script src>`, `<link rel="stylesheet">` and `<link rel="modulepreload">` it references. Manifest, icons, external URLs and lazy chunks are not counted. A referenced file missing from the build fails the measurement instead of counting as zero bytes.
- `--json` prints the breakdown; `--summary <file>` writes the `journeys.<journey>.counters` shape a ratchet checker will consume (next PR).
- New Nx project `performance-tools` (test + lint targets), Tier B in `tools/coverage/coverage-policy.json`, root scripts `perf:initial-bytes` and `perf:tools:test`.
- New contract `docs/architecture/performance-journeys.md`, linked from the validation map, the agent context map and the README.
- **Review follow-ups:** resources are deduplicated by request URL (query kept, fragment dropped); `index.html` is parsed with parse5 (already a repository dependency, scripting enabled), so comments, bogus comments, raw-text bodies (script/style/noscript/title/textarea), inert `<template>` contents and character references in attributes all follow the HTML5 algorithm instead of a hand-written scanner; the review's edge cases stay as regression tests; docs show the `pnpm --silent` form for JSON output and explain how the counter relates to Angular's rounded "Initial total".
- **Found while measuring:** the environment files and the playback diagnostic panel imported the whole `package.json` (`import packageJson from '@package'`), which esbuild cannot tree-shake, so `main.js` carried the complete file and the counter moved with every script or dependency edit. They now import `{ version }` only (
|
||
|
|
faad8fd8fd |
docs(agents): compact root guidance and preserve task-specific knowledge (#1645)
* docs(agents): compact root guidance and preserve task-specific knowledge * fix(agents): parse guidance navigation with Markdown tokens * fix(agents): validate generic literal repository paths * fix(agents): distinguish code symbols and shortcut images * fix(agents): recognize SCSS filename literals * fix(agents): handle fenced imports and encoded paths * fix(agents): parse prose and rendered HTML anchors * fix(agents): validate rendered HTML navigation * fix(agents): use GitHub-compatible heading slugs * fix(agents): require standalone top-level Claude import * fix(agents): exclude HTML-contained guidance imports * fix(agents): handle image fragments and quoted imports * fix(agents): validate visible HTML and image source sets * fix(agents): recognize package scopes and route source work * fix(agents): parse JSONC and constrain package exemptions * fix(agents): decode link entities and allow package subpaths * fix(agents): route source work and check extensionless files * fix(agents): support package versions and source fragments * fix(agents): accept qualified package prose * fix(agents): retain rendered context for Markdown references * fix(agents): validate visible headings and spaced paths * fix(agents): validate media and hyphenated literal paths * fix(agents): decode full HTML entities and media assets * fix(agents): recognize possessive package mentions * fix(agents): validate extensionless imports and version comparators * fix(agents): retain visible backticks and explicit path punctuation * fix(agents): validate image-map navigation targets * fix(agents): count all Markdown line endings in budgets * fix(agents): delimit package prose at Unicode punctuation * fix(agents): normalize punctuation for extensionless imports * fix(agents): preserve filenames across prose punctuation * fix(agents): validate iframe document references * fix(agents): inspect document suffix before URL fragments * fix(agents): unify Markdown suffix and encoded import guards * fix(agents): handle wildcard versions and alternate documents * fix(agents): validate document formats and trim HTML URLs * fix(agents): cover document families and guidance basenames * fix(agents): require files for media references * fix(agents): preserve block boundaries and validate embeds * fix(agents): normalize internal HTML URL whitespace * fix(agents): reject empty media and ignore URL at-signs * fix(agents): validate srcdoc references and empty srcset * fix(agents): honor HTML bases and preserve adjacent imports * fix(agents): convert base file URLs to native paths * fix(agents): preserve imports after bare URL punctuation * fix(agents): exclude opaque URI prose from import scans * fix(agents): keep import tokens outside URI scheme matches * fix(agents): restrict opaque URI exemptions to parsed links * fix(agents): handle opening prose delimiters * fix(agents): scan nested imports and share document suffixes * fix(agents): reject pathless media and direct file URLs * fix(agents): reject file bases and preserve quoted URL boundaries * fix(agents): distinguish URL quotes and cover guidance variants * fix(agents): validate SVG images and conventional guides * fix(agents): handle declared package names handles and SVG use * fix(agents): normalize closing punctuation on federated handles * fix(agents): normalize Unicode punctuation on handles * fix(agents): normalize possessive federated handles * fix(agents): separate parenthetical prose from handles * fix(agents): exclude www autolinks from import scanning * ci: allow manual CodeQL validation of PR branches * fix(agents): reject nonportable Windows drive links |
||
|
|
6f7973a9fb |
feat(updater): nightly builds and a stable/nightly update channel (#1608)
* feat(updater): nightly builds and a stable/nightly update channel Every master push publishes its artifacts as a prerelease of 4gray/iptvnator-nightly instead of the rolling test-master draft, with a version of <next patch>-nightly.<commit date>.<run number> applied in every build job. Settings → About gains an Update channel switch; AppUpdateService re-points electron-updater per check (feed repository, allowPrerelease, channel name, allowDowngrade reset) and reads release notes from the repository the requested version belongs to. Channel switches are forward-only: a nightly build stays until a newer stable release exists. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(updater): compute the nightly version once and keep re-runs safe Review follow-ups: the nightly version is resolved by a leading job and handed to every build job, and the patch is bumped only when the base tag already exists so the release-cut window stays below the imminent release. A re-run never deletes a published nightly; only a draft left by a failed run is replaced. Typed update-status literals in the remaining specs carry the new channel fields. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(packaging): expect the nightly-version prerequisite in the build workflow graph Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
e9eca1c386 |
chore(deps): upgrade Angular to 22.1 and Nx to 23.2 (#1603)
* chore(deps): upgrade Angular to 22.1 and Nx to 23.2 * fix(deps): complete Angular migrations after rebasing on master * fix(ci): use the Node pin for Windows runtime refresh * docs(deps): synchronize the workspace-shell Node requirements |
||
|
|
40092f6efc | docs(tmdb): clarify user-provided API key policy (#1581) | ||
|
|
fa9084fca3 |
feat(shell): startup window mode, --fullscreen switch and F11 toggle (#1514)
Settings > General gains "Window on startup" (normal / maximized / fullscreen), Electron only, mirrored into the main-process config by SETTINGS_UPDATE and applied at the next window creation. `--fullscreen` forces one fullscreen launch (consumed by the first window). F11 toggles OS-level fullscreen through WINDOW:TOGGLE_FULLSCREEN — the exit path on Windows/Linux where the title bar is hidden — and is skipped while the player owns document.fullscreenElement. attachWindowStateEvents tracks native and HTML fullscreen as two flags, since Electron leaves only the HTML state when the window was already natively fullscreen. macOS ignores the constructor `fullscreen` option on a hidden window, so ready-to-show repeats the request after show(). Toggles are decided by an observe-only, event-fed tracker (native-fullscreen-transitions.ts), never against isFullScreen(). Closes #1455 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
728df1a68c |
fix(packaging): restore Snap desktop runtime (#1406)
* fix(packaging): restore Snap desktop runtime * docs(packaging): publish Snap launch repair note * fix(packaging): declare Node 22.12 floor * docs(architecture): update SQLite pin rationale * fix(tooling): align Node engine floor * fix(tooling): constrain supported Node releases * docs(architecture): correct node-abi consumer |
||
|
|
e5bb127ede |
fix(playback): make playback keyboard shortcuts work without shared controls (#1398)
* fix(playback): make playback keyboard shortcuts work without shared controls With the default configuration (Video.js, webPlayerSharedControls off) the playback shortcuts advertised in the in-app help and README — Space/K, F, arrow seek/volume, M — silently did nothing: ControlsShortcuts only exists inside app-player-controls, which never renders on the preference-off path. Attach a LegacyPlayerShortcuts wrapper (same arbitration and ignore rules) in the vendor-chrome HTML5, Video.js, and ArtPlayer players, forwarding the commands to each engine's own API. Seek stays gated on authoritative VOD metadata plus a finite positive duration, and a visible playback diagnostic disables the keys. The legacy ArtPlayer chrome now passes hotkey:false — its focus-scoped vendor hotkeys ignore defaultPrevented and would double-handle every key — with its Escape-exits-web-fullscreen behavior restored by the new wiring. The playback entries in the in-app shortcut help and README drop their embedded-MPV-only qualifier, since the keys now work in every runtime. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014h2cZi5DcFSbcmV7WgB6qB * fix(playback): restore audible volume when M unmutes at zero volume Addresses the Codex review finding on #1398: after arrowing the volume down to zero (which mutes), M flipped muted off while leaving the volume at 0, so the player looked unmuted but stayed silent — in all three legacy engine adapters. Mirror the shared controls' ControlsVolume semantics with a per-adapter LegacyMuteMemory: muting remembers the audible volume, and unmuting while the volume sits at zero restores it, with the same 0.5 fallback when nothing was remembered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014h2cZi5DcFSbcmV7WgB6qB --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b94f40dc74 |
feat(i18n): add Hungarian translation (hu) (#1236)
Integrate the community-contributed Hungarian translation by Tibor Hermann (@htibcsike) as the 19th locale: - add apps/web/src/assets/i18n/hu.json (1,142 of 1,175 keys translated; 32 keys added after the contribution fall back to English, plus the new LANGUAGES.HUNGARIAN endonym) - register HUNGARIAN = 'hu' in the Language enum, SUPPORTED_LANGS, Angular date locale registration, and the TMDB language map (hu-HU) - add LANGUAGES.HUNGARIAN = "Magyar" to en.json and all other locales via tools/i18n/fill-missing.mjs - update README.md and CLAUDE.md language counts to 19 Closes #1192, refs #1140. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
59c15493a7 |
docs: sync CLAUDE.md, AGENTS.md and architecture docs with actual code
Full audit of CLAUDE.md, AGENTS.md, README.md and docs/architecture/ against the codebase; every fix is backed by current code: - remove documented-but-unimplemented IPTVNATOR_DISABLE_HARDWARE_ACCELERATION flag (no reads anywhere in apps/, libs/, tools/) - CLAUDE.md: add epg_channel_mappings to the schema table list - m3u-playlist-module: *-tab dirs -> *-view (+recent-view), selectActivePlaylist, real PlaylistState shape, ChannelEpgMetadata instead of removed EnrichedChannel, actual /workspace/playlists routes, per-view outputs, live-epg-panel-state key - workspace-dashboard: per-rail Settings.dashboardRails toggles, three missing rails in the diagram, split live-favorites/recent-live rails, welcome-dashboard empty-state type, RECENTLY_WATCHED_LIVE_TV title key - stalker-portal: CategoryContentViewComponent for vod/series, collection-route components for favorites/recent, corrected series-view/favorites-button paths, actor/:personId route, epg panel selectors - category-management: reloadCategories lives in with-content.feature.ts, workspace-context-panel owns the dialog, XtreamPendingRestoreService flow - stalker-mock-server (+app README): scenario-seeded faker, resetAll() clears content cache too, ordinal season episode ids, handlers/ dir location - sqlite-db-worker: cancellation shipped (drop from out-of-scope), full operations module list - portal-detail-navigation: replace three removed component paths - tmdb-metadata-enrichment: details cache keys are id:<tmdbId>|v2 - electron-security: CSP frame-src youtube-nocookie exception, sandbox: !frameCopyExperiment nuance - download-manager: libs/portal/xtream instead of xtream-electron folder, data-driven downloads nav, drop removed app-search-result-item note - playlist-backup-restore: settings-backup facade owns the import handoff - workspace-shell: functional workspaceEntryRedirect, playlists route children - iptvnator-ui-guidelines: EPG card radius 11px, detail-view mixin is `base` - embedded-mpv-native, player-controls-contract: minor precision fixes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
beb62db314 |
feat(settings): add shared web player controls toggle (#1198)
* docs(playback): design shared controls setting * docs(playback): plan shared controls setting * feat(settings): persist shared web controls preference * test(settings): harden shared controls normalization coverage * feat(settings): expose shared web controls toggle * fix(settings): label shared controls toggle * feat(playback): resolve shared controls from settings * test(playback): cover shared controls setting * docs(playback): document shared controls preference * fix(playback): await settings before host creation * fix(settings): normalize shared controls updates |
||
|
|
baad440602 |
docs(readme): keep Codecov badge in for-the-badge style (shields), pin master
Revert to the shields Codecov badge for visual consistency with the other for-the-badge badges (native badge has no matching style). Pin branch=master, add the Codecov logo, and keep the canonical app.codecov.io link. Note: the shields Codecov proxy is occasionally slow and may briefly render 'unknown'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
c7683479a2 |
docs(readme): use native Codecov badge and canonical link
The shields.io Codecov proxy intermittently rendered 'unknown' (extra API hop, cached by GitHub's camo). Switch to Codecov's native master branch badge and point the link at app.codecov.io directly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
0acb29d244 |
docs(readme): add Ko-fi to the donations buttons, fix Sponsors label
The single donation button used a 'Buy Me A Coffee' image that actually linked to GitHub Sponsors. Split it into two correctly labeled badges: GitHub Sponsors and Ko-fi (ko-fi.com/4gray, same as the blog). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
f7ba68dc13 |
docs(readme): fix stale CI badge (build-and-test.yaml -> ci.yml)
The workflow status badge pointed at a non-existent build-and-test.yaml. Point it at the actual CI workflow, link it to the workflow page, and label it CI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
e75aad44c8 |
docs(readme): regroup features by theme, add TMDB/embedded MPV/downloads/global search
Replace the flat feature list with seven themed groups, drop granular release-note-level items, and surface recent capabilities (TMDB enrichment, embedded MPV, download manager, global search, dashboard, auto-updater, remote control) with desktop-only markers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
73b7905668 |
docs(website): warn about unofficial IPTVnator sites; tidy README languages
- Add blog post warning about scam sites misusing the IPTVnator name to sell IPTV services or push suspicious downloads (official sources + safety) - Add "official sources only" callout to README linking to the post - Replace enumerated language list with a count (18) linking to i18n files Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
e801028005 |
feat(epg): support playlist-scoped sources
Add playlist-scoped EPG source support for M3U playlists. |
||
|
|
4094a36fef | Harden Linux embedded MPV packaging (#1043) | ||
|
|
e8aa7c3a34 |
[codex] Add scoped EPG security trust controls (#1054)
* Add scoped EPG security trust controls * fix: address scoped trust review feedback --------- Co-authored-by: 4gray <fourgray@proton.me> |
||
|
|
2c032cd3c8 |
fix(security): complete Electron hardening and review follow-ups
* fix(security): harden Electron IPC against MITM, SSRF, path and injection risks S1 TLS: validate certs by default on playlist/EPG fetches (opt-out via IPTVNATOR_ALLOW_INSECURE_TLS); new util/secure-https.ts. S2: write-file IPC restricted to save-dialog-authorized paths. S3: XTREAM_PROBE_URL guarded by assertRemoteUrlAllowed + maxRedirects:0; new events/url-safety.ts (+19 tests). S4: EPG titles rendered via interpolation, not [innerHTML]. S5: downloads reveal/play limited to recorded download paths. S6: Stalker cmd encoded (slash-preserving) to block query injection. EPG-worker and Stalker fetches reject file://-style/credentialed URLs; LAN/self-hosted targets remain allowed. * perf(player): lazy-load web video players via @defer Wrap Video.js/HTML5/ArtPlayer in @defer (on immediate) so video.js, hls.js, artplayer and mpegts.js split into a deferred chunk loaded on first playback instead of eagerly on the player route. Embedded MPV (native) stays eager. Spec uses DeferBlockBehavior.Playthrough. * fix(player): remove leaked HTML video listeners on destroy volumechange used a mismatched removeEventListener reference, while loadedmetadata and timeupdate were never removed at all. Bind all three to stable handler fields used for both add and remove, and add a teardown regression test asserting each listener is detached on destroy. * refactor(dashboard): extract pure navigation helpers from DashboardDataService Move the 8 stateless link/navigation-state/type-kind helpers into a new dashboard-navigation.util.ts so the routing logic is independently testable and the 1260-line god-service shrinks. DashboardDataService keeps the public methods as thin delegators (facade) so the public API and the single consumer (workspace-dashboard-rails) are unchanged. First slice of the DashboardDataService decomposition; verified by the existing service spec (33/33) and the app typecheck. * fix(review): address PR feedback (IPv6 link-local, write-path cap, @defer placeholder) - url-safety: broaden IPv6 link-local detection to the full fe80::/10 range (fe80:: through febf::), not just the fe80:: prefix (+ regression tests). - playlist.events: cap authorizedWritePaths (evict oldest past 32) so a save dialog opened without a following write cannot accumulate entries until restart. - web-player-view: add a @placeholder to each @defer (on immediate) player block to avoid the one-frame blank/layout-shift before the chunk resolves. * fix(security): close Electron network and download gaps * test(downloads): cover cancellation and restart cleanup * fix(downloads): address Greptile review gaps * test(security): reproduce remaining Greptile findings * fix(security): close remaining Greptile findings * test(downloads): reproduce early database queue stall * fix(downloads): release queue after setup failures * test(downloads): reproduce completion queue stall * fix(downloads): release queue after completion failures |
||
|
|
55efc24608 | fix(pwa): harden self-hosted runtime boundaries | ||
|
|
b2909a1186 |
feat(workspace): add keyboard shortcuts help (#957)
* feat(workspace): add keyboard shortcuts help * style(workspace): polish keyboard shortcuts dialog * fix(workspace): address shortcuts review feedback * docs(workspace): clarify desktop shortcut availability * refactor(workspace): split keyboard shortcut definitions |
||
|
|
fd59447064 |
docs(docker): restore self-hosted PWA compose flow
* docs(docker): restore self-hosted PWA compose flow * fix(docker): supervise self-hosted runtime processes * fix(docker): exit cleanly on container stop |
||
|
|
e7db920543 |
fix(settings): clarify external player path hints (#929)
* fix(settings): clarify external player path hints * fix(settings): gate external player hints to desktop |
||
|
|
6cbf2a1cfb |
feat(m3u): add context menus to recent channels
Entire-Checkpoint: f957cd9849e0 |
||
|
|
0bcb89aaaf |
docs: add TRADEMARK.md clarifying name and logo are not MIT-licensed
Adds a top-level TRADEMARK.md spelling out that the IPTVnator name and logo are unregistered trademarks reserved to the project, separate from the MIT-licensed source code, and documenting what forks may and may not do plus where to report misuse. README gets a short Trademark section pointing to the new file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 3f453cc0085a |
||
|
|
fa8f719c74 |
docs(screenshots): update and add new screenshots for enhanced documentation
Entire-Checkpoint: c6e522b4276c |
||
|
|
d51ea4b2b0 |
docs(README): add instructions for starting Electron with an isolated data directory
Entire-Checkpoint: c6e522b4276c |
||
|
|
e25c137eec |
build: include X11 override in Snap package configuration
Entire-Checkpoint: 21b9b9f90f23 |
||
|
|
7cc25380dd | docs: update EPG documentation to reflect new bulk handling and fallback logic | ||
|
|
f4f1993434 | docs: update settings and documentation for enhanced agent guidance and radio player support | ||
|
|
61d34aef7a | chore: switch to pnpm | ||
|
|
7931127aa6 | chore(website): enhance SEO with structured data and meta tags in various components | ||
|
|
1e3cfaedce | docs: update README to include additional links for support and donations | ||
|
|
cf5d88a4b4 |
docs: add troubleshooting section for macOS & Linux
Add a Troubleshooting section to the README covering two common platform-specific launch issues and how to resolve them. - macOS: document Gatekeeper "App is damaged and can't be opened" and show how to clear the quarantine flag with xattr. - Linux: document chrome-sandbox SUID permission error and provide two solutions — fix permissions (chown/chmod) for packaged installs, or run with --no-sandbox by editing the desktop launcher or using the command line. These instructions reduce user confusion and support requests by providing clear, actionable steps to get IPTVnator running on affected systems. |
||
|
|
e079d268f0 | docs: add CLAUDE.md with developer guidance | ||
|
|
07a21dde45 | feat: add polish language | ||
|
|
ba6eaaac77 | chore: return back to the classic way with languages | ||
|
|
aa323b1eda | chore: update README to reflect migration from Electron to Tauri and improve formatting | ||
|
|
bda754d56a |
Update README.md
add homebrew install |
||
|
|
138e8a3027 | chore: update readme file | ||
|
|
b93287db98 | docs: update README to include installation instructions for Gentoo |