fix(nav): a linked player, file and search survive what the app does by itself (review round 2)

- Playlists: a link over a playlist the account does not hold plays alone; the rail's own
  fallback no longer counts as a pick that gives the player up.
- The linked opener opens or clears nothing once the address names another player, and stops
  looking (and fetching its row) once it settled.
- Files: a list the server's cap cut — the sorted or searched one too — keeps the link, with a
  notice of its own (EN/HU).
- History: an in-place view rewrite (search refine, count, channel to channel, sub-view)
  drops a reloaded entry's overlay marks (replaceView); a landing on a channel opened from a
  search keeps the search; the Feed press on a search opens the plain feed.
- Files/Playlists: an emptied search is written at once.
- Client log: plf/cf (column text filters) are typed keys; the shell has none of its own.
- Contract: writers outside lib/ take a literal or a lib/ function's result; every URL key is
  sorted typed or not; the writer regexes are held by a fixture; sub-views named; the bookmark
  net reads page tags.
- Feed defaults in one constant; one live-search queue helper; Scheduler writes through the
  Activity codec; notRun copy (EN/HU) names Forward and a plain "Search YouTube" button.
This commit is contained in:
2026-10-27 16:08:52 +01:00
parent 0d5c6273f1
commit 6d57d62ac8
38 changed files with 717 additions and 187 deletions
@@ -227,6 +227,34 @@ describe("NavigationProvider · the YouTube search in the address", () => {
expect(search().get("yt")).toBeNull();
});
it("the Feed press on the search itself opens the plain feed, as over a channel", () => {
const { result } = render();
act(() => result.current.enterYtSearch("lofi"));
const depth = window.history.length;
act(() => result.current.setPage("feed"));
expect(result.current.ytSearch).toBeNull();
expect(search().get("yt")).toBeNull();
expect(window.history.length).toBe(depth + 1); // its own entry: Back returns to the search
});
it("a landing on a channel opened from the search (an overlay over it closing) keeps the search", () => {
const { result } = render();
act(() => result.current.enterYtSearch("lofi"));
act(() => result.current.openChannel("UC1"));
// A player over the channel closed: the step back lands on the channel's own entry.
act(() => {
window.dispatchEvent(new PopStateEvent("popstate", { state: window.history.state }));
});
expect(result.current.ytSearch).toBe("lofi");
expect(result.current.ytSearchAsked).toBe(true);
// Back off the channel: the search it stood over is still the one asked for.
window.history.replaceState({ sfPage: "feed", _yt: "lofi" }, "", "/?page=feed&yt=lofi");
act(() => {
window.dispatchEvent(new PopStateEvent("popstate", { state: window.history.state }));
});
expect(result.current.ytSearchAsked).toBe(true);
});
it("a channel closed in place over the search names the search again", () => {
const { result } = render();
act(() => result.current.enterYtSearch("lofi"));