Navigation up/back split, faster startup, and CI versionCode fix
🏗️ 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>
This commit is contained in:
2026-07-17 21:12:36 +02:00
co-authored by Claude Opus 4.8
parent 1992a8187d
commit 2e479d05b3
13 changed files with 278 additions and 94 deletions
+50 -36
View File
@@ -139,20 +139,23 @@ function createAuthStore() {
update((s) => ({ ...s, isLoading: true, error: null }));
try {
// Check security status
try {
const securityStatus = await commands.storageGetSecurityStatus();
console.log("[Auth] Security status:", securityStatus);
if (!securityStatus.usingKeyring) {
update((s) => ({
...s,
securityWarning:
"Credentials are stored with reduced security (encrypted file instead of system keyring).",
}));
// Check security status — fire-and-forget. It only sets a warning banner,
// so it must not sit in front of session restore (and thus first paint).
void (async () => {
try {
const securityStatus = await commands.storageGetSecurityStatus();
console.log("[Auth] Security status:", securityStatus);
if (!securityStatus.usingKeyring) {
update((s) => ({
...s,
securityWarning:
"Credentials are stored with reduced security (encrypted file instead of system keyring).",
}));
}
} catch (error) {
console.warn("[Auth] Failed to get security status:", error);
}
} catch (error) {
console.warn("[Auth] Failed to get security status:", error);
}
})();
// Initialize auth manager and get session
console.log("[Auth] Initializing auth manager...");
@@ -162,24 +165,30 @@ function createAuthStore() {
if (session) {
console.log("[Auth] Restoring session for user:", session.username, "on server:", session.serverUrl);
// Create RepositoryClient for cache-first access
// Create RepositoryClient for cache-first access. This IS required before
// we mark authenticated — the first screen (library overview) reads
// through it — so keep it awaited.
repository = new RepositoryClient();
await repository.create(session.serverUrl, session.userId, session.accessToken, session.serverId);
// Configure Jellyfin client in Rust player for automatic playback reporting
const deviceId = await getDeviceId();
try {
console.log("[Auth] Configuring Rust player with restored session...");
await commands.playerConfigureJellyfin(
session.serverUrl,
session.accessToken,
session.userId,
deviceId
);
console.log("[Auth] Rust player configured for automatic playback reporting");
} catch (error) {
console.error("[Auth] Failed to configure Rust player:", error);
}
// Configure the Rust player for playback reporting. This is NOT needed to
// render the first screen (it only matters once playback starts), so run
// it fire-and-forget instead of blocking first paint on two more IPC
// round-trips (getDeviceId + playerConfigureJellyfin).
void (async () => {
try {
const deviceId = await getDeviceId();
await commands.playerConfigureJellyfin(
session.serverUrl,
session.accessToken,
session.userId,
deviceId
);
console.log("[Auth] Rust player configured for automatic playback reporting");
} catch (error) {
console.error("[Auth] Failed to configure Rust player:", error);
}
})();
// Set authenticated immediately (offline-first)
set({
@@ -211,14 +220,19 @@ function createAuthStore() {
console.error("[Auth] Failed to start connectivity monitoring:", error);
});
// Start background session verification
try {
const verifyDeviceId = await getDeviceId();
await commands.authStartVerification(verifyDeviceId);
console.log("[Auth] Background verification started");
} catch (error) {
console.error("[Auth] Failed to start verification:", error);
}
// Start background session verification — fire-and-forget. This is
// already asynchronous work (results arrive via the auth:* events wired
// above), so awaiting getDeviceId + authStartVerification here only
// delayed first paint by two IPC round-trips for no UI benefit.
void (async () => {
try {
const verifyDeviceId = await getDeviceId();
await commands.authStartVerification(verifyDeviceId);
console.log("[Auth] Background verification started");
} catch (error) {
console.error("[Auth] Failed to start verification:", error);
}
})();
} else {
// No stored session
console.log("[Auth] No active session found");