fix(spa): back button respects history; auto-redirect / → /hjem when logged in

Two related navigation fixes:

1. Back-button now uses real browser history. Sub-view "Tilbake"
   buttons (permalink, tag page, personvern, public list) all used
   to call goPublicHome() — which always sent the user to / —
   instead of returning to wherever they came from. Replaced with
   a single backToCallerOrHome() that delegates to
   navigate.goBack(fallback), which calls window.history.back() if
   there's a prior entry, else navigates to /hjem (or / when
   anonymous) so cold-loaded permalinks still have somewhere to go.

2. Cold-load redirect: a logged-in user landing on / probably
   wants their own dashboard, not the public marketing surface.
   After the me-probe finishes in onMount, if the initial route
   was 'public-home' and session.user exists, push to /hjem
   instead of applying the public-home route. This only runs on
   initial mount (not on every popstate), so browser-back from
   /hjem to / still works if the user explicitly navigates there.

The wordmark in the nav also picks its destination by auth state
now — logged-in users go to /hjem, anonymous users to /. Otherwise
clicking it post-redirect would just bounce back to /hjem.
This commit is contained in:
Ole-Morten Duesund 2026-05-25 18:48:34 +02:00
commit 443702d222
2 changed files with 46 additions and 17 deletions

View file

@ -3,6 +3,7 @@
import { ready } from './lib/crypto';
import { api, ApiError } from './lib/api';
import { session, setSessionUserOnly } from './lib/session.svelte';
import { goBack } from './lib/navigate';
import { logout } from './lib/auth';
import Login from './components/Login.svelte';
import Signup from './components/Signup.svelte';
@ -138,7 +139,18 @@
// No session — fine.
}
applyRoute(route);
// Cold-load redirect: a logged-in user landing on the public landing
// probably wants their own dashboard, not the marketing-y "what is this"
// page. We only redirect on this initial mount — not on every
// applyRoute call — so browser-back from /hjem to / still lets the
// explicit navigation through (no loop, and the wordmark intentionally
// sends logged-in users to /hjem instead of / anyway).
if (route.view === 'public-home' && session.user) {
pushUrl('/hjem');
view = 'home';
} else {
applyRoute(route);
}
});
function applyRoute(route: Route) {
@ -164,22 +176,20 @@
}
}
function leaveTag() {
// Same logic as leavePersonvern — back to wherever they were.
if (session.user) goHome();
else goPublicHome();
}
function goPersonvern() {
pushUrl('/personvern');
view = 'personvern';
}
function leavePersonvern() {
// Send the visitor wherever they "would have been" — landing if logged out,
// dashboard if logged in. Either is more useful than staying on the doc page.
if (session.user) goHome();
else goPublicHome();
/**
* Back-button handler for sub-views (permalink, tag page, personvern,
* public list). Uses real browser history so the user returns to
* wherever they came from in the SPA — /hjem, /etiketter/foo,
* /aktivitet/bar, anywhere. Falls back to /hjem (or / when anonymous)
* on cold-loads where there's no prior history entry.
*/
function backToCallerOrHome() {
goBack(session.user ? '/hjem' : '/');
}
function onAuthed() {
@ -207,7 +217,8 @@
<main>
<nav class="top">
<h1 style="margin: 0;">
<a href="/" onclick={(e) => { e.preventDefault(); goPublicHome(); }}
<a href={session.user ? '/hjem' : '/'}
onclick={(e) => { e.preventDefault(); session.user ? goHome() : goPublicHome(); }}
style="color: inherit; text-decoration: none;">Vinterliste</a>
</h1>
{#if view !== 'public-list' && view !== 'permalink' && view !== 'tag'}
@ -246,9 +257,9 @@
{#if view === 'loading'}
<p class="muted">Laster …</p>
{:else if view === 'public-list'}
<PublicList username={publicListUsername} onBack={goPublicHome} />
<PublicList username={publicListUsername} onBack={backToCallerOrHome} />
{:else if view === 'permalink'}
<ActivityPermalink id={activityId} onBack={goPublicHome} />
<ActivityPermalink id={activityId} onBack={backToCallerOrHome} />
{:else if view === 'public-home'}
<Home publicOnly={true} />
{:else if view === 'login'}
@ -276,9 +287,9 @@
{:else if view === 'moderate-tags'}
<ModerateTags onDone={goHome} />
{:else if view === 'personvern'}
<Personvern onBack={leavePersonvern} />
<Personvern onBack={backToCallerOrHome} />
{:else if view === 'tag'}
<TagPage tag={tagName} onBack={leaveTag} />
<TagPage tag={tagName} onBack={backToCallerOrHome} />
{:else}
<Home publicOnly={false} />
{/if}

View file

@ -34,3 +34,21 @@ export function onSpaLink(e: MouseEvent, path: string): void {
e.preventDefault();
navigate(path);
}
/**
* "Back" navigation for in-app back buttons. Uses real browser history
* when there's prior history to return to (preserves the user's actual
* path through the SPA). Falls back to navigate(fallback) on cold-loads
* when the user opened the URL directly in a new tab and there's no
* prior entry to back into.
*
* `window.history.length` is browser-dependent but length === 1 is a
* reliable cold-load signal: every browser counts the current entry.
*/
export function goBack(fallback: string): void {
if (window.history.length > 1) {
window.history.back();
} else {
navigate(fallback);
}
}