diff --git a/frontend/src/lib/userNamespace.ts b/frontend/src/lib/userNamespace.ts new file mode 100644 index 0000000000..4839e46f6e --- /dev/null +++ b/frontend/src/lib/userNamespace.ts @@ -0,0 +1,31 @@ +import { get } from 'svelte/store' +import { userStore } from '$lib/stores' + +/** + * Workspace username for the authed user, suitable as the `u/{X}/...` + * namespace in editor draft paths. + * + * The `/add` → `/edit/u/{username}/draft_{uuid}` redirects in each + * editor's `+page.ts` run during SvelteKit's load phase — BEFORE the + * (logged) layout's async `getUserExt` populates `userStore`. Reading + * `get(userStore)?.username` from there returns `undefined` on every + * fresh nav, falling back to the `'me'` placeholder and producing + * `u/me/draft_{uuid}` paths instead of the real namespace. + * + * The layout writes `username` to `localStorage` on every successful + * `getUserExt`, so this helper: + * 1. Reads `userStore` first (live value when it IS populated, e.g. + * same-tab re-navs after the first load). + * 2. Falls back to `localStorage` (warm-cache from the previous + * session — covers the load-phase race on cold reload). + * 3. Falls back to `'me'` (true first-ever load with no session yet). + */ +export function getUsernameForNamespace(): string { + const live = get(userStore)?.username + if (live) return live + try { + const cached = localStorage.getItem('username') + if (cached) return cached + } catch {} + return 'me' +} diff --git a/frontend/src/routes/(root)/(logged)/+layout.svelte b/frontend/src/routes/(root)/(logged)/+layout.svelte index bab8e8f4b0..9200d7bee4 100644 --- a/frontend/src/routes/(root)/(logged)/+layout.svelte +++ b/frontend/src/routes/(root)/(logged)/+layout.svelte @@ -136,6 +136,16 @@ if (!deepEqual(user, $userStore)) { userStore.set(user) } + // Persist the workspace username so the synchronous `/add` → + // `/edit/u/{user}/draft_{uuid}` redirects (in each editor's + // `+page.ts`) can land on the right namespace without waiting + // for this async layout fetch. Without this they fall back to + // the `'me'` placeholder on every fresh nav. + try { + if (user?.username) localStorage.setItem('username', user.username) + } catch (e) { + console.error('Could not persist username to local storage', e) + } if (isCloudHosted() && user?.is_admin) { isPremiumStore.set(await WorkspaceService.getIsPremium({ workspace })) } diff --git a/frontend/src/routes/(root)/(logged)/apps/add/+page.ts b/frontend/src/routes/(root)/(logged)/apps/add/+page.ts index d311b9ac2f..7410b2c42e 100644 --- a/frontend/src/routes/(root)/(logged)/apps/add/+page.ts +++ b/frontend/src/routes/(root)/(logged)/apps/add/+page.ts @@ -1,7 +1,6 @@ import { redirect } from '@sveltejs/kit' import { base } from '$app/paths' -import { get } from 'svelte/store' -import { userStore } from '$lib/stores' +import { getUsernameForNamespace } from '$lib/userNamespace' import type { PageLoad } from './$types' // See /scripts/add/+page.ts for the rationale — redirect at load time @@ -10,7 +9,7 @@ import type { PageLoad } from './$types' export const prerender = false export const load: PageLoad = ({ url }) => { - const username = get(userStore)?.username ?? 'me' + const username = getUsernameForNamespace() const uuid = crypto.randomUUID() const params = new URLSearchParams(url.searchParams) params.set('new_draft', 'true') diff --git a/frontend/src/routes/(root)/(logged)/apps_raw/add/+page.ts b/frontend/src/routes/(root)/(logged)/apps_raw/add/+page.ts index e57a7443f6..d9af04e8b1 100644 --- a/frontend/src/routes/(root)/(logged)/apps_raw/add/+page.ts +++ b/frontend/src/routes/(root)/(logged)/apps_raw/add/+page.ts @@ -1,7 +1,6 @@ import { redirect } from '@sveltejs/kit' import { base } from '$app/paths' -import { get } from 'svelte/store' -import { userStore } from '$lib/stores' +import { getUsernameForNamespace } from '$lib/userNamespace' import type { PageLoad } from './$types' // See /scripts/add/+page.ts for the rationale — redirect at load time @@ -10,7 +9,7 @@ import type { PageLoad } from './$types' export const prerender = false export const load: PageLoad = ({ url }) => { - const username = get(userStore)?.username ?? 'me' + const username = getUsernameForNamespace() const uuid = crypto.randomUUID() const params = new URLSearchParams(url.searchParams) params.set('new_draft', 'true') diff --git a/frontend/src/routes/(root)/(logged)/flows/add/+page.ts b/frontend/src/routes/(root)/(logged)/flows/add/+page.ts index 56ea4bd08b..1962eb61ec 100644 --- a/frontend/src/routes/(root)/(logged)/flows/add/+page.ts +++ b/frontend/src/routes/(root)/(logged)/flows/add/+page.ts @@ -1,7 +1,6 @@ import { redirect } from '@sveltejs/kit' import { base } from '$app/paths' -import { get } from 'svelte/store' -import { userStore } from '$lib/stores' +import { getUsernameForNamespace } from '$lib/userNamespace' import type { PageLoad } from './$types' // See /scripts/add/+page.ts for the rationale — redirect at load time @@ -10,7 +9,7 @@ import type { PageLoad } from './$types' export const prerender = false export const load: PageLoad = ({ url }) => { - const username = get(userStore)?.username ?? 'me' + const username = getUsernameForNamespace() const uuid = crypto.randomUUID() const params = new URLSearchParams(url.searchParams) params.set('new_draft', 'true') diff --git a/frontend/src/routes/(root)/(logged)/scripts/add/+page.ts b/frontend/src/routes/(root)/(logged)/scripts/add/+page.ts index 1cb4b3d984..38350ee488 100644 --- a/frontend/src/routes/(root)/(logged)/scripts/add/+page.ts +++ b/frontend/src/routes/(root)/(logged)/scripts/add/+page.ts @@ -1,7 +1,6 @@ import { redirect } from '@sveltejs/kit' import { base } from '$app/paths' -import { get } from 'svelte/store' -import { userStore } from '$lib/stores' +import { getUsernameForNamespace } from '$lib/userNamespace' import type { PageLoad } from './$types' // Redirect at the load phase, before the +page.svelte ever mounts. The @@ -9,17 +8,11 @@ import type { PageLoad } from './$types' // one frame before navigating, hence the white flash; doing the work in // `load` lets SvelteKit cancel the route transition and head straight // for the edit page. -// -// `userStore` is populated client-side by the (logged) layout, so by -// the time this load runs (always after layout setup) it usually has -// the real username. The `'me'` fallback is the same one the original -// onMount used — keeps behavior identical for the rare race where the -// store hasn't filled yet. export const prerender = false export const load: PageLoad = ({ url }) => { - const username = get(userStore)?.username ?? 'me' + const username = getUsernameForNamespace() const uuid = crypto.randomUUID() const params = new URLSearchParams(url.searchParams) params.set('new_draft', 'true')