🏗️ Build and Test JellyTau / Run Tests (push) Successful in 4m57s
Traceability Validation / Check Requirement Traces (push) Successful in 22s
Build & Release / Run Tests (push) Successful in 5m13s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 4m30s
Build & Release / Build Linux (push) Successful in 17m52s
Build & Release / Build Android (push) Failing after 58s
Build & Release / Create Release (push) Has been skipped
Navigation: - Split conflated "back" into navigateUp (deterministic route parent) and a history-safe navigateBack that tracks in-app depth via afterNavigate instead of history.length. Fixes the resume-from-background trap where a stale WebView stack left the header arrow stuck on the current page. - /library self-corrects for music/tv/movies (which have dedicated landing pages): a leftover currentLibrary no longer forces the inline content-list view, so "up"/back shows the libraries overview. Live TV / channels / other types still render inline. Startup (unblock first paint): - auth.initialize() no longer awaits security-status, player-config, or session verification before flipping isInitialized. These run fire-and-forget after the session is restored, so the library overview paints without waiting on several serial IPC round-trips. Versioning / CI: - tauri.conf.json + package.json aligned to 0.0.15 (the tag series had drifted to 0.1.0, whose formula-derived versionCode 1000 outran the v0.0.x tags). - Release workflow now pins a monotonic Android versionCode (1000 + major*10000 + minor*100 + patch) so tagged builds never downgrade below prior installs and always increase in semver order. Tests: navigation (4), auth (29), playbackMode (23) green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
96 lines
3.9 KiB
TypeScript
96 lines
3.9 KiB
TypeScript
import { goto, afterNavigate } from "$app/navigation";
|
|
|
|
/**
|
|
* App navigation has two distinct affordances (per the Android guidelines):
|
|
*
|
|
* - **Up** — move to the current screen's *logical parent* in the app
|
|
* hierarchy (e.g. `/library/music/albums` → `/library/music`). Deterministic,
|
|
* derived from the route, and never depends on how the user got here. This is
|
|
* what the in-app header arrows should do almost everywhere.
|
|
*
|
|
* - **Back** — pop the *actual* history stack: return to wherever the user came
|
|
* from, which may be a sibling branch (a detail page reached from search vs.
|
|
* from the library) or even outside the app. This is the hardware/gesture
|
|
* back button's job; use it in-app only where "return to origin" is genuinely
|
|
* better than Up (e.g. a detail page with many entry points).
|
|
*
|
|
* The old single `navigateBack` conflated the two: it called `history.back()`
|
|
* first and only fell back to a path. On resume-from-background the WebView can
|
|
* restore a history stack whose `length` is still > 1 but which cannot actually
|
|
* go back within the app — so `history.back()` no-ops and the user is trapped on
|
|
* the page. Splitting Up (pure `goto`) from Back (tracked in-app depth) removes
|
|
* that trap: Up can never get stuck, and Back only fires when we *know* there is
|
|
* an in-app entry to return to.
|
|
*/
|
|
|
|
// In-app navigation depth, maintained via the public `afterNavigate` hook rather
|
|
// than reading SvelteKit's internal history-state key. Starts at 0 (the entry
|
|
// page). Each forward in-app navigation increments it; a popstate (back/forward
|
|
// gesture) sets it to the delta-adjusted value. When it is > 0 we know a real
|
|
// in-app Back exists and won't strand the user — independent of the WebView's
|
|
// possibly-stale `history.length` after a background/restore.
|
|
let inAppDepth = 0;
|
|
let navHookRegistered = false;
|
|
|
|
/**
|
|
* Register the navigation-depth tracker. Call once from the root layout's
|
|
* component init (afterNavigate must run in a component context). Safe to call
|
|
* more than once — only the first registration takes effect.
|
|
*/
|
|
export function registerNavigationTracking(): void {
|
|
if (navHookRegistered) return;
|
|
navHookRegistered = true;
|
|
|
|
afterNavigate((nav) => {
|
|
// A popstate (hardware/gesture back or forward) carries a delta; apply it so
|
|
// depth tracks the true stack position. Programmatic goto/link navigations
|
|
// have no delta and move one step deeper.
|
|
const delta = nav.delta;
|
|
if (typeof delta === "number") {
|
|
inAppDepth = Math.max(0, inAppDepth + delta);
|
|
} else if (nav.from) {
|
|
// A real forward navigation from an existing page (not the initial load).
|
|
inAppDepth += 1;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reset the tracked depth. Intended for tests only, so each case starts from a
|
|
* known baseline (module state persists across a test file otherwise).
|
|
*/
|
|
export function __resetNavigationDepthForTest(): void {
|
|
inAppDepth = 0;
|
|
}
|
|
|
|
/**
|
|
* True when there is at least one in-app history entry to pop. Unlike
|
|
* `history.length > 1`, this reflects navigations that happened *within this app
|
|
* session*, so a stale WebView stack after a background/restore can't fool it.
|
|
*/
|
|
export function canGoBack(): boolean {
|
|
return inAppDepth > 0;
|
|
}
|
|
|
|
/**
|
|
* **Up**: go to the given logical parent path. Always deterministic; never
|
|
* consults history, so it cannot trap the user. Prefer this for header arrows.
|
|
*/
|
|
export function navigateUp(parentPath: string): void {
|
|
goto(parentPath);
|
|
}
|
|
|
|
/**
|
|
* **Back**: return to the previous in-app page when there is one, otherwise fall
|
|
* back to `fallbackPath` (typically the logical parent) so the user is never
|
|
* stranded. Use only where returning to the exact origin is preferable to Up
|
|
* (e.g. a detail page reachable from multiple branches).
|
|
*/
|
|
export function navigateBack(fallbackPath: string): void {
|
|
if (canGoBack()) {
|
|
history.back();
|
|
} else {
|
|
goto(fallbackPath);
|
|
}
|
|
}
|