mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
Merge origin/master into the programme guide branch
Adopts the compact icon-only timeline toolbar (#1557): the Guide button is now an icon-only `epg-timeline__iconbtn` with its label in the tooltip and aria-label, so the unused EPG.TIMELINE.GUIDE key is dropped from all locales. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
commit
49fdd40c9d
123 files changed
+5976
-474
No files matched your search
@@ -494,7 +494,7 @@ Video.js HTTP error is `network-error` and shows its status. Because an HTTP
|
||||
status is server/network evidence rather than decoding evidence, external
|
||||
decoding is not presented as a likely fix.
|
||||
|
||||
Video.js `8.23.9`, the default web player, runs HTTP streaming through bundled
|
||||
Video.js `8.24.0`, the default web player, runs HTTP streaming through bundled
|
||||
VHS `3.17.5`. Its terminal `Player#error` crosses a separate allowlisted
|
||||
boundary built only from the public Video.js `MediaError` code/status,
|
||||
`metadata.errorType`, and the documented `player.tech().vhs` runtime property.
|
||||
@@ -841,7 +841,12 @@ original headers or credentials required to reconstruct a safe launch request.
|
||||
|
||||
No recommendation mutates `Settings.player` or another persisted setting.
|
||||
Recovery recommendations never auto-switch a player or source and do not
|
||||
replace the separate source-owner auto-failover feature. Attempts, overrides,
|
||||
replace the separate source-owner auto-failover feature. The narrow Xtream
|
||||
live Auto format contract is separate: a source owner may supply one advertised
|
||||
TS transport to the same web player on an initial terminal HTTP failure. It
|
||||
never chooses another engine, and its pending callback waits for the old
|
||||
transport's render teardown. See [Initial Auto HLS failure](./xtream-portal-compatibility.md#initial-auto-hls-failure)
|
||||
for eligibility, session ownership and the external/Embedded MPV/VHS limits. Attempts, overrides,
|
||||
resume handoff, and diagnostics are session-local: there is no persistent
|
||||
history, cross-session learning, correlation, or telemetry.
|
||||
|
||||
|
||||
@@ -267,8 +267,8 @@ remain local when the meaning is explicit.
|
||||
|
||||
### Collapsible Live Sidebar
|
||||
|
||||
- M3U, Xtream, and Stalker live layouts share a single sidebar collapse toggle
|
||||
that hides the channels rail to give the player and EPG full width.
|
||||
- M3U, Xtream, and Stalker live layouts share a sidebar collapse toggle that
|
||||
hides the channels rail to give the player and EPG full width.
|
||||
- Xtream Live TV's root view (`/live` with no selected category) follows the
|
||||
same paginated `All Items` shell as VOD and Series: a widget header with the
|
||||
total channel count, page-size controls, and page navigation above the shared
|
||||
@@ -285,15 +285,41 @@ remain local when the meaning is explicit.
|
||||
is `live` (Xtream) or `itv`/`radio` (Stalker); movies, series, favorites,
|
||||
and recent routes leave it untouched.
|
||||
- Collapsed state is owned by `LiveLayoutSidebarStateService`
|
||||
(`providedIn: 'root'`) in `@iptvnator/portal/shared/util`. Every surface that
|
||||
participates injects the service and reads `isCollapsed`; any toggle calls
|
||||
`service.toggle()`. Persistence delegates to the existing
|
||||
`live-sidebar-state` helpers, so the localStorage key stays unchanged and
|
||||
missing/invalid values restore to expanded.
|
||||
- A `mat-icon-button` with `chevron_left` lives in the sidebar header and
|
||||
toggles state. While collapsed, a floating `chevron_right` mini-fab appears
|
||||
at the left edge of `.content-container` to restore the rail (and the
|
||||
categories rail, in Xtream/Stalker live).
|
||||
(`providedIn: 'root'`) in `@iptvnator/portal/shared/util` and kept **per
|
||||
surface** (`LiveSidebarSurface`): `m3u` (the M3U player), `portal` (Xtream
|
||||
and Stalker live layouts plus the shell categories rail) and `collection`
|
||||
(the unified favorites/recent live tab). Every participant injects the
|
||||
service, holds `isCollapsedFor(surface)` (a stable signal) and calls
|
||||
`toggle(surface)`; nothing reads localStorage directly. Persistence lives
|
||||
under `live-sidebar-state:<surface>`. Hiding the list is a per-context
|
||||
choice: it must not follow the user from a portal to an M3U playlist, nor
|
||||
from the desktop rail to the phone bottom drawer of another surface. The
|
||||
pre-split shared key `live-sidebar-state` is forgotten on service
|
||||
construction and never read — a stored `collapsed` there hid every channel
|
||||
list in the app behind a 32px chevron and survived restart, "Remove all
|
||||
playlists" and re-import (issue #1458).
|
||||
- The control never moves. Inside the rail a `mat-icon-button` with
|
||||
`chevron_left` hides it; while collapsed a floating `chevron_right` mini-fab
|
||||
sits at the left edge of `.content-container`. Because both of those live
|
||||
in the thing they hide, the workspace header additionally renders
|
||||
`view_sidebar` (`headerSidebarToggle`, `WorkspaceShellHeaderService`) on
|
||||
every route that renders its own rail — M3U `all`/`groups`, Xtream `live`,
|
||||
Stalker `itv`/`radio` (`resolveRouteLiveSidebarSurface`). It stays in place
|
||||
in both states, uses `aria-pressed` (pressed = rail visible) and tints
|
||||
primary only while the rail is hidden, since the hidden state is the
|
||||
exception that deserves the cue. Collection pages are deliberately excluded:
|
||||
only the page knows whether its live tab, and therefore the rail, is on
|
||||
screen, so its own header toggle beside the content switch stays the owner.
|
||||
At the phone breakpoint (≤640px) the header toggle is hidden: the rail is a
|
||||
bottom drawer there with its own toggle and the header has no spare width.
|
||||
- While the rail is collapsed and nothing is playing, every live host renders
|
||||
`app-channel-list-hidden-state` (`@iptvnator/portal/shared/ui`) instead of
|
||||
the "select a channel" empty state: a title that says the list is hidden, a
|
||||
one-line hint naming the shortcut, and a full-size "Show channels list"
|
||||
stroked button wired to the same toggle. The generic
|
||||
`app-portal-empty-state` grew optional `hint`, `actionLabel`, `actionIcon`
|
||||
inputs and an `action` output for this; the action keeps full opacity while
|
||||
icon and copy stay muted, because it is the way out of the state.
|
||||
- Keyboard shortcut: `Cmd/Ctrl+B`. The handler ignores events that originate
|
||||
inside `<input>`, `<textarea>`, `<select>`, or content-editable elements via
|
||||
the shared `isTypingInInput` helper.
|
||||
|
||||
@@ -836,12 +836,22 @@ activation, and the details dialog behave identically to the timeline.
|
||||
(**vertical title**, no time) → `micro` (just a marker); (C) a **hover/focus
|
||||
popover** revealing the full title + time + description for any non-`wide`
|
||||
block (it flips above the block when the panel is near the screen bottom);
|
||||
(D) a px-per-minute **zoom** slider (tick density adapts via
|
||||
`timelineTickStepForScale`); and (E) **grouping** of ≥4 consecutive short
|
||||
(D) a px-per-minute **zoom** (tick density adapts via
|
||||
`timelineTickStepForScale`): the toolbar's icon-only zoom button cycles
|
||||
three presets — day overview (`1`) → by hour (`1.75`, the default) →
|
||||
detailed (`3.4`, the scale a group chip expands to) — snapping a
|
||||
wheel-tuned scale to its band's successor (`TIMELINE_ZOOM_LEVELS`,
|
||||
`nextTimelineZoomScale`), while Ctrl/⌘ + wheel (and trackpad pinch) over
|
||||
the ribbon zooms continuously around the cursor within
|
||||
`TIMELINE_ZOOM_MIN..MAX` and `preventDefault`s so Chromium never page-zooms
|
||||
the same gesture; the current level lives in the tooltip/`aria-label` and
|
||||
a `data-zoom-level` attribute; and (E) **grouping** of ≥4 consecutive short
|
||||
(<10 min) programmes into one dashed "N short" chip when zoomed out
|
||||
(`scale < TIMELINE_GROUP_ZOOM_MAX`), expanded by clicking it. The ribbon
|
||||
canvas lives in the child `app-epg-timeline-track`; the parent owns the
|
||||
scroller, toolbar (incl. the zoom slider) and state.
|
||||
scroller, toolbar (icon-only "Now" + zoom buttons, both labelled through
|
||||
tooltip + `aria-label`, so the channel/programme heading takes every pixel
|
||||
the fixed-width controls leave) and state.
|
||||
- **Panel height & titles.** Block titles wrap onto as many lines as the card
|
||||
height allows and are clipped (not single-line ellipsis); the foot ("ON NOW"
|
||||
tag / "Watch") stays pinned at the bottom. With an inline player the guide is
|
||||
@@ -1518,7 +1528,6 @@ resolve another retained source sharing that channel ID. Legacy programmes with
|
||||
unknown (`NULL`) ownership are conservatively left alone; the existing database
|
||||
initialization backfill handles rows whose channel still identifies their owner.
|
||||
|
||||
|
||||
Renderer reconciliation fences lookups before its first asynchronous step.
|
||||
Imports wait for serialized reconciliation (including playlist migration), then
|
||||
filter against its committed owner set. Completion increments the data revision again
|
||||
|
||||
@@ -126,6 +126,36 @@ publishes it when the active channel is cleared IN PLACE (e.g. quitting an
|
||||
external MPV/VLC session dispatches `resetActiveChannel` while the route
|
||||
stays mounted).
|
||||
|
||||
## Live channel return and playback order
|
||||
|
||||
Xtream and Stalker live views keep a component-owned playback queue through
|
||||
`LiveChannelPlaybackQueue` in `portal-shared-data-access`. Explicit selection
|
||||
captures the actual displayed order, including search/sort and a fullscreen
|
||||
panel's own filter. Remote up/down, numeric selection and the published channel
|
||||
number use that queue while category or search browsing remains independent.
|
||||
Xtream history handoffs from global search or Recently Added capture the
|
||||
eligible destination category in the selected channel sort order; an unrelated
|
||||
previous category/query does not define that queue. Explicit All Items clicks
|
||||
still capture their displayed list. Same-channel replay and remote selection preserve it. Source/type changes and
|
||||
view destruction discard it; ITV and radio never share an owner.
|
||||
|
||||
Stalker captures before asynchronous URL resolution and commits only the winning
|
||||
successful request. Paged lists extend the queue only as more rows arrive for
|
||||
the original category and search scope. They do not fetch a global catalog for
|
||||
remote navigation. Xtream excludes removed streams and hidden or removed
|
||||
categories from eligible queue entries.
|
||||
|
||||
A conditional **Show playing channel** icon in the channel header appears when
|
||||
the playing channel is absent from the browsed results and its category remains
|
||||
accessible. It clears `q` and the store query, returns to that category, expands
|
||||
the sidebar, then scrolls and focuses the playing row. It never starts playback
|
||||
or changes the playback/session/catchup identity. A collapsed sidebar first uses
|
||||
its existing restore action. Removed categories are not recreated or unhidden.
|
||||
Stalker reuses the already-resolved playing item as a temporary normal row when
|
||||
it lies beyond loaded provider pages. This row is deduplicated once it arrives
|
||||
in provider results and discarded on browsing or playback changes; returning
|
||||
never crawls the catalog. Raw provider rows alone extend the playback queue.
|
||||
|
||||
## Shared helpers
|
||||
|
||||
- File: `libs/portal/shared/util/src/lib/remote-channel-navigation.ts`
|
||||
@@ -184,10 +214,10 @@ Implemented behavior:
|
||||
- `onRemoteControlCommand` for number select
|
||||
- Up/down:
|
||||
- Uses selected live item `selectedItem().xtream_id`
|
||||
- Navigates inside `selectItemsFromSelectedCategory()`
|
||||
- Navigates inside the captured eligible playback queue
|
||||
- Calls `playLive(nextItem, true)` so remote actions explicitly start playback
|
||||
- Number select:
|
||||
- Maps number to item in current category list
|
||||
- Maps number to item in the same captured eligible queue
|
||||
- Calls `playLive(channel, true)` so remote actions explicitly start playback
|
||||
- Publishes status via effect:
|
||||
- `portal: 'xtream'`
|
||||
@@ -207,10 +237,10 @@ Implemented behavior:
|
||||
- `onRemoteControlCommand` for number select
|
||||
- Up/down:
|
||||
- Uses `selectedItem().id`
|
||||
- Navigates inside `itvChannels()`
|
||||
- Navigates inside the captured ITV/radio playback queue
|
||||
- Calls `playChannel(nextItem, true)` so remote actions explicitly start playback
|
||||
- Number select:
|
||||
- Maps number into `itvChannels()`
|
||||
- Maps number into the same captured playback queue
|
||||
- Calls `playChannel(channel, true)` so remote actions explicitly start playback
|
||||
- Publishes status via effect:
|
||||
- `portal: 'stalker'`
|
||||
|
||||
@@ -1274,9 +1274,10 @@ The Stalker live route and radio route intentionally share
|
||||
in flight belong to the selection and survive the switch. Only a section
|
||||
change (`itv` ↔ `radio`, where the route session clears the selection)
|
||||
invalidates that request and drops the fallback. A playing channel outside
|
||||
the newly selected category simply has no highlighted row, and remote
|
||||
channel up/down finds no neighbour until a channel from the visible list is
|
||||
played.
|
||||
the newly selected category can be revealed with **Show playing channel**
|
||||
in the channel header. Remote up/down, numeric selection and status retain
|
||||
the captured playback order while browsing. See the
|
||||
[queue and reveal contract](./remote-control.md#live-channel-return-and-playback-order).
|
||||
|
||||
## Full ITV Channel List Cache
|
||||
|
||||
|
||||
@@ -154,6 +154,11 @@ is watching keeps playing while the sidebar re-filters (Xtream: #936; Stalker:
|
||||
series category clicks do drop the open detail (`setSelectedItem(null)` /
|
||||
`clearSelectedItem()`) because they navigate to a list route.
|
||||
|
||||
The channel header offers **Show playing channel** when browsing excludes the
|
||||
active channel. It returns to that category and focuses the row without
|
||||
restarting playback; remote commands retain captured playback order. See the
|
||||
[queue and reveal contract](./remote-control.md#live-channel-return-and-playback-order).
|
||||
|
||||
## Search And Navigation Rules
|
||||
|
||||
Search is shell-owned and route-aware:
|
||||
|
||||
@@ -129,6 +129,58 @@ Manual `ts` and `m3u8` settings remain supported; when a manual setting is not
|
||||
allowed by the portal, URL construction falls back to the first
|
||||
provider-allowed format.
|
||||
|
||||
### Initial Auto HLS failure
|
||||
|
||||
The routed Xtream Live TV host attaches `ResolvedPortalPlayback.liveAutoTsUrl`
|
||||
only when the current user preference is Auto and the current account explicitly
|
||||
advertises both HLS and TS. `XtreamUrlService.constructAutoLiveTsUrl` uses the
|
||||
same credential encoding and provider-subpath URL builder as the initial URL;
|
||||
no URL string replacement, manifest preflight, stream download, or API probe is
|
||||
introduced. Unknown/empty formats and manual HLS/TS have no alternative.
|
||||
|
||||
`WebPlayerLiveAutoFormat` may consume that alternative once per mounted logical
|
||||
live session, in the same selected web player, after an owned terminal network
|
||||
diagnostic with HTTP 4xx/5xx, before the native video element reports `playing`.
|
||||
A successful manifest is not playback. HLS key/media/unknown-stage failures,
|
||||
DRM payloads, content-info VOD/episodes, and catch-up are excluded. Generic
|
||||
network failures, status zero, cancellation, timeouts without an HTTP response,
|
||||
and decode errors keep the normal explicit recovery surface.
|
||||
|
||||
The old application is first removed from the render tree. The next render
|
||||
callback starts TS only after the web engine's synchronous loader teardown and
|
||||
only while source, logical session and user intent still match. The TS payload
|
||||
keeps the original headers, title and playback metadata; Electron applies them
|
||||
through its existing scoped header handoff. HTTP-media requests retain the
|
||||
existing transport/trust rules and do not feed new evidence or admission into
|
||||
the separate portal-API host-connectivity guard. Recovery recommendations still
|
||||
require a user action and never auto-switch engines. No settings or playlist
|
||||
cache is changed. A TS failure displays normal recovery actions; Retry repeats
|
||||
TS without rearming the automatic attempt. Changing the actual logical channel
|
||||
or playlist, or mounting a new host, creates a new session; same-session metadata
|
||||
or provider-source refresh does not rearm it. Same-channel replay and metadata
|
||||
refresh preserve the chosen TS while the candidate URLs, headers and mode stay
|
||||
the same; an actual transport/provider change discards that selection. This deliberately avoids a sticky
|
||||
playlist preference and reevaluates advertised formats on the next channel.
|
||||
|
||||
| Player/path | Initial Auto failure support |
|
||||
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| HTML5 / ArtPlayer, hls.js | One advertised TS attempt after terminal HLS manifest/level/segment HTTP failure; waits for the engine's own bounded retries |
|
||||
| Video.js / VHS | Only an observable terminal HTTP diagnostic can trigger an attempt. A sole HLS rendition's failing segment can remain in VHS retry/exclusion cycles without such a diagnostic; use manual TS for that case |
|
||||
| External MPV / VLC | Manual TS only. Process launch/exit and best-effort playing telemetry do not reliably identify the initial HLS media HTTP failure |
|
||||
| Embedded MPV | Manual TS only; no equivalent structured HTTP diagnostic |
|
||||
| Unified Favorites/Recent live resolution | Manual TS; it does not own fresh session account-format evidence |
|
||||
|
||||
For manual recovery, stop the current player, choose **Settings > Playback >
|
||||
Stream Format > ts**, save, and reopen the channel. This keeps the chosen player
|
||||
and avoids a second uncorrelated external process. The settings description
|
||||
explains this route. This is partial playback coverage of #1513, not a claim
|
||||
that all external-player and VHS scenarios automatically recover.
|
||||
|
||||
Synthetic regression media lives in the mock's `live-fallback:live-fallback`
|
||||
account: HLS manifest 200 plus segment 403, manifest 403, a playable local
|
||||
H.264/AAC TS alternative, a failing TS alternative, and a delayed HLS segment.
|
||||
Tests assert actual video progress and the number of concrete TS requests.
|
||||
|
||||
If stored Xtream playback credentials contain an invalid server URL or blank
|
||||
username/password, stream URL construction returns an empty URL instead of
|
||||
throwing during playback.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Live channel return implementation plan
|
||||
|
||||
> **For agentic workers:** Use subagent-driven-development for the independently owned Xtream integration, with spec and code review. Execute the shared queue, Stalker integration and validation in this task. Do not merge the resulting PR.
|
||||
|
||||
**Goal:** Keep live remote navigation stable during category/search browsing and provide one conditional action to reveal the playing channel, without duplicating its title or restarting playback (#1520).
|
||||
|
||||
**Architecture:** Each live host owns a playback queue scoped by source and content type. Explicit channel activation captures the displayed ordered list; remote commands preserve it. Stalker extends the captured queue only when more rows arrive for the same browsing scope, never by falling back to a global ITV cache. Revealing restores the channel's available category and clears search without invoking playback. Provider-neutral queue state belongs in portal/shared/data-access; portal routing, visibility and scrolling remain in their feature libraries.
|
||||
|
||||
**Tech Stack:** Angular signals, Nx/Jest, Playwright, Electron IPC, existing Material icon buttons and translations.
|
||||
|
||||
## Contract
|
||||
|
||||
- Capture the list before asynchronous playback resolution; commit the queue only for the winning successful request. Same active-channel replays/catch-up return keep the queue.
|
||||
- Source/type changes invalidate old queue ownership. Numeric, adjacent and status all read the same queue; remote commands never recapture the browsed category.
|
||||
- A captured paged Stalker queue contains loaded rows. Newly loaded rows extend it only while source/type/category/search scope still matches. No background all-portal crawling; unavailable pages are not advertised as loaded channels.
|
||||
- Xtream uses the actual sidebar sort/search order and filters hidden/removed categories and channels from navigation eligibility. Revealing never unhides categories.
|
||||
- An out-of-filter channel gets a localized `CHANNELS.SHOW_PLAYING_CHANNEL` action in the existing list header. No now-playing title block or EPG changes. Reuse the existing sidebar restore action while collapsed.
|
||||
- Reveal clears interfering query state, selects the active channel's accessible category, waits for rows/rendering and scrolls/focuses the scroll owner. Stale navigation/loading must not reselect a previous channel or restart the player. For paged Stalker search, reuse the already-resolved channel as a scoped normal row until provider results include it, rather than crawling the catalog.
|
||||
- Fullscreen selection captures its own displayed filtered list; existing fullscreen controls and playback/session ownership remain intact. No redesign of the fullscreen panel.
|
||||
- Stalker #1543 remains independent: queue capture consumes the actual list and makes no new global-search policy.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] Add `LiveChannelPlaybackQueue<T>` and focused tests under `libs/portal/shared/data-access/src/lib/`; export via the public barrel. Test capture/fallback, source/type ownership, preserved order, same-scope extension, stale-scope rejection, unchanged-snapshot identity and reset. Run the focused Jest target red, then green.
|
||||
- [x] Extend Xtream live layout and channel-list integration with regression coverage: category/search/sort drift, numeric/status/adjacent parity, hidden category exclusion, reveal without playback calls, route category/query handling, fullscreen list capture. Keep new logic in focused feature files where needed for max-lines. Run `pnpm nx test portal-xtream-feature --runInBand` and lint.
|
||||
- [x] Extend Stalker live layout with the same queue policy and reveal action. Cover ITV/radio ID collisions, asynchronous successful/failed/stale resolution, paged queue extension, cache render windows, category/search drift, and reveal without session reset. Run `pnpm nx test portal-stalker-feature --runInBand` and lint.
|
||||
- [x] Add `CHANNELS.SHOW_PLAYING_CHANNEL` to all shipped locale dictionaries. Keep icon button tooltip and accessible name identical.
|
||||
- [x] Extend closest web and Electron E2E flows. Verify real remote HTTP commands preserve the original queue after browsing changes, and reveal retains the same video/session. Check both portal UIs in light/dark with synthetic sources only.
|
||||
- [x] Update `docs/architecture/remote-control.md`, `stalker-portal.md`, `workspace-shell.md`, and mirrored AGENTS.md/CLAUDE.md guidance. Add one `.changes/portals-live-channel-return.md` note and validate it.
|
||||
- [x] Run affected unit targets, E2E, lint, Electron E2E build, release-note validator and `git diff --check`. Complete independent spec review, then code-quality review and resolve findings.
|
||||
- [ ] Commit, create a PR and complete CI/review checks to ready-to-merge without merging, as authorized in this task's original scope.
|
||||
|
||||
## Validation commands
|
||||
|
||||
```sh
|
||||
pnpm nx test portal-shared-data-access --runInBand
|
||||
pnpm nx test portal-xtream-feature --runInBand
|
||||
pnpm nx test portal-stalker-feature --runInBand
|
||||
pnpm nx run-many -t lint -p portal-shared-data-access,portal-xtream-feature,portal-stalker-feature
|
||||
pnpm nx run electron-backend:build-e2e --parallel=1
|
||||
pnpm run release:notes:validate
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Choose atomized existing web/Electron E2E targets after inspecting project discovery and their runner configuration. Record any environment limitations rather than treating a build or unit pass as full runtime validation.
|
||||
|
||||
## Validation record
|
||||
|
||||
- Stalker data-access after the category-search merge: 546 tests — passed.
|
||||
- Shared data-access: 169 unit tests; Xtream: 445; Stalker: 355 — passed.
|
||||
- Electron build and all three affected library lint targets — passed (existing warnings only).
|
||||
- Electron remote-control suite: 5/5, including both portals and Stalker radio;
|
||||
Chromium category-switch/reveal regression: 1/1 — passed.
|
||||
- New regressions exposed the original queue drift, same-URL reveal cancellation,
|
||||
and virtual viewport attachment race before their fixes.
|
||||
- Independent spec and quality reviews passed after resolving provider-search
|
||||
ownership, beyond-page reveal and delayed-playback page reconciliation.
|
||||
- Release notes and diff whitespace validated. Tested UI in light/dark using
|
||||
synthetic mock portals. Platform-specific Windows/Linux packaged runs and
|
||||
Firefox/WebKit were not needed for the shared renderer/IPC-navigation change.
|
||||
|
||||
- Synced with category-scoped Stalker search (#1552); added auto-open queue regressions for global search/Recently Added after Codex review.
|
||||
Reference in new issue
Block a user