diff --git a/web/src/lib/components/auth/LegalFooter.svelte b/web/src/lib/components/auth/LegalFooter.svelte
new file mode 100644
index 00000000..7b728f8c
--- /dev/null
+++ b/web/src/lib/components/auth/LegalFooter.svelte
@@ -0,0 +1,51 @@
+
+
+{#if cloudMode}
+
+{/if}
+
+
diff --git a/web/src/lib/stores/auth.svelte.ts b/web/src/lib/stores/auth.svelte.ts
index 0fd7f86d..92d0086f 100644
--- a/web/src/lib/stores/auth.svelte.ts
+++ b/web/src/lib/stores/auth.svelte.ts
@@ -2,6 +2,16 @@ import { api, type AuthSession } from '$lib/api/client';
let session = $state(null);
let loading = $state(false);
+// Coalesces concurrent load() calls so the root layout and auth-page onMount
+// (register, forgot-password) can both request the session without firing
+// duplicate /auth/session requests — or, worse, having a late failure from a
+// duplicate fetch overwrite a successful fetch's session=null.
+let inflight: Promise | null = null;
+// Bumps on clear(). Any fetch started in a previous generation is stale: its
+// success must not resurrect a logged-out session, and its finally must not
+// clobber a new inflight that started after clear(). Readers only write
+// state when the generation they captured at fetch-start still matches.
+let generation = 0;
export const authStore = {
get session() { return session; },
@@ -12,20 +22,48 @@ export const authStore = {
get loading() { return loading; },
async load() {
+ if (inflight) return inflight;
loading = true;
- try {
- session = await api.auth.session();
- } catch (err) {
- session = null;
- loading = false;
- throw err; // Re-throw so callers can distinguish fetch errors from "not authenticated".
- } finally {
- loading = false;
- }
- return session;
+ const myGeneration = generation;
+ const isCurrent = () => generation === myGeneration;
+ inflight = api.auth.session()
+ .then((s) => {
+ if (isCurrent()) session = s;
+ return session;
+ })
+ .catch((err) => {
+ if (isCurrent()) session = null;
+ throw err; // Re-throw so callers can distinguish fetch errors from "not authenticated".
+ })
+ .finally(() => {
+ if (isCurrent()) {
+ loading = false;
+ inflight = null;
+ }
+ });
+ return inflight;
+ },
+
+ // ensureLoaded returns the cached session when one exists, otherwise fetches it.
+ // Use this on auth pages (register, forgot-password, etc.) that navigate in via
+ // SPA routing after the user has logged out — logout clears the store, and the
+ // root layout's one-shot onMount doesn't re-run on subsequent navigation, so a
+ // page that relies on session fields (e.g. cloud_mode) would otherwise see
+ // stale nulls and render the self-hosted branch on Pad Cloud. Concurrent calls
+ // coalesce through load()'s in-flight promise.
+ async ensureLoaded() {
+ if (session) return session;
+ return this.load();
},
clear() {
session = null;
+ generation++;
+ // Drop the in-flight promise reference so the next ensureLoaded()/load()
+ // call fires a fresh fetch rather than attaching to a pre-logout request.
+ // The old promise may still resolve/reject in the background; the
+ // generation guard above prevents it from writing to any state.
+ inflight = null;
+ loading = false;
}
};
diff --git a/web/src/routes/forgot-password/+page.svelte b/web/src/routes/forgot-password/+page.svelte
index e26208d8..c8b71ef5 100644
--- a/web/src/routes/forgot-password/+page.svelte
+++ b/web/src/routes/forgot-password/+page.svelte
@@ -1,11 +1,23 @@