fix: redirect /add routes at load phase to eliminate white flash

This commit is contained in:
Diego Imbert
2026-06-04 15:43:27 +02:00
parent 8972ceb07f
commit adcaee889f
8 changed files with 102 additions and 96 deletions
@@ -1,22 +1,5 @@
<script lang="ts">
// `/apps/add` is a thin redirect onto the canonical editor at
// `/apps/edit/u/{user}/draft_{uuid}?new_draft=true`. See /scripts/add
// for the design rationale — all editor logic lives in
// /apps/edit/[...path]. The `u/{user}/` prefix matches Windmill's
// path scheme so the draft slot lives in the authed user's namespace.
import { goto } from '$lib/navigation'
import { page } from '$app/state'
import { userStore } from '$lib/stores'
import { get } from 'svelte/store'
import { onMount } from 'svelte'
onMount(() => {
const username = get(userStore)?.username ?? 'me'
const uuid = crypto.randomUUID()
const params = new URLSearchParams(page.url.searchParams)
params.set('new_draft', 'true')
goto(`/apps/edit/u/${username}/draft_${uuid}?${params.toString()}`, {
replaceState: true
})
})
</script>
<!--
Never rendered — the sibling +page.ts redirects at the load phase, before
this component can mount. Kept as an empty file because SvelteKit only
registers a route when a +page.svelte exists.
-->
@@ -0,0 +1,18 @@
import { redirect } from '@sveltejs/kit'
import { base } from '$app/paths'
import { get } from 'svelte/store'
import { userStore } from '$lib/stores'
import type { PageLoad } from './$types'
// See /scripts/add/+page.ts for the rationale — redirect at load time
// to avoid mounting a blank route component and flashing white.
export const prerender = false
export const load: PageLoad = ({ url }) => {
const username = get(userStore)?.username ?? 'me'
const uuid = crypto.randomUUID()
const params = new URLSearchParams(url.searchParams)
params.set('new_draft', 'true')
redirect(307, `${base}/apps/edit/u/${username}/draft_${uuid}?${params.toString()}`)
}
@@ -1,22 +1,5 @@
<script lang="ts">
// `/apps_raw/add` is a thin redirect onto the canonical editor at
// `/apps_raw/edit/u/{user}/draft_{uuid}?new_draft=true`. See
// /scripts/add for the design rationale — all editor logic lives in
// /apps_raw/edit/[...path]. The `u/{user}/` prefix matches Windmill's
// path scheme so the draft slot lives in the authed user's namespace.
import { goto } from '$lib/navigation'
import { page } from '$app/state'
import { userStore } from '$lib/stores'
import { get } from 'svelte/store'
import { onMount } from 'svelte'
onMount(() => {
const username = get(userStore)?.username ?? 'me'
const uuid = crypto.randomUUID()
const params = new URLSearchParams(page.url.searchParams)
params.set('new_draft', 'true')
goto(`/apps_raw/edit/u/${username}/draft_${uuid}?${params.toString()}`, {
replaceState: true
})
})
</script>
<!--
Never rendered — the sibling +page.ts redirects at the load phase, before
this component can mount. Kept as an empty file because SvelteKit only
registers a route when a +page.svelte exists.
-->
@@ -0,0 +1,18 @@
import { redirect } from '@sveltejs/kit'
import { base } from '$app/paths'
import { get } from 'svelte/store'
import { userStore } from '$lib/stores'
import type { PageLoad } from './$types'
// See /scripts/add/+page.ts for the rationale — redirect at load time
// to avoid mounting a blank route component and flashing white.
export const prerender = false
export const load: PageLoad = ({ url }) => {
const username = get(userStore)?.username ?? 'me'
const uuid = crypto.randomUUID()
const params = new URLSearchParams(url.searchParams)
params.set('new_draft', 'true')
redirect(307, `${base}/apps_raw/edit/u/${username}/draft_${uuid}?${params.toString()}`)
}
@@ -1,22 +1,5 @@
<script lang="ts">
// `/flows/add` is a thin redirect onto the canonical editor at
// `/flows/edit/u/{user}/draft_{uuid}?new_draft=true`. See /scripts/add
// for the design rationale — all editor logic lives in
// /flows/edit/[...path]. The `u/{user}/` prefix matches Windmill's
// path scheme so the draft slot lives in the authed user's namespace.
import { goto } from '$lib/navigation'
import { page } from '$app/state'
import { userStore } from '$lib/stores'
import { get } from 'svelte/store'
import { onMount } from 'svelte'
onMount(() => {
const username = get(userStore)?.username ?? 'me'
const uuid = crypto.randomUUID()
const params = new URLSearchParams(page.url.searchParams)
params.set('new_draft', 'true')
goto(`/flows/edit/u/${username}/draft_${uuid}?${params.toString()}`, {
replaceState: true
})
})
</script>
<!--
Never rendered — the sibling +page.ts redirects at the load phase, before
this component can mount. Kept as an empty file because SvelteKit only
registers a route when a +page.svelte exists.
-->
@@ -0,0 +1,18 @@
import { redirect } from '@sveltejs/kit'
import { base } from '$app/paths'
import { get } from 'svelte/store'
import { userStore } from '$lib/stores'
import type { PageLoad } from './$types'
// See /scripts/add/+page.ts for the rationale — redirect at load time
// to avoid mounting a blank route component and flashing white.
export const prerender = false
export const load: PageLoad = ({ url }) => {
const username = get(userStore)?.username ?? 'me'
const uuid = crypto.randomUUID()
const params = new URLSearchParams(url.searchParams)
params.set('new_draft', 'true')
redirect(307, `${base}/flows/edit/u/${username}/draft_${uuid}?${params.toString()}`)
}
@@ -1,30 +1,6 @@
<script lang="ts">
// `/scripts/add` is a thin redirect onto the canonical editor at
// `/scripts/edit/u/{user}/draft_{uuid}?new_draft=true`. All editor logic
// — fetch, seed handling, autosave wiring — lives in
// `/scripts/edit/[...path]`; keeping it in one place means a single
// source of truth for the editor's load lifecycle.
//
// The `u/{user}/` prefix matches Windmill's path scheme so the
// draft slot lives in the authed user's namespace from the start.
// `new_draft=true` tells the edit page "first mount, don't fetch the
// non-existent item; seed empty." The flag is consumed and stripped
// from the URL by the edit page on first render. Any other query
// params (`template`, `hub`, `tutorial`, ...) ride along untouched so
// existing entry-points keep working.
import { goto } from '$lib/navigation'
import { page } from '$app/state'
import { userStore } from '$lib/stores'
import { get } from 'svelte/store'
import { onMount } from 'svelte'
onMount(() => {
const username = get(userStore)?.username ?? 'me'
const uuid = crypto.randomUUID()
const params = new URLSearchParams(page.url.searchParams)
params.set('new_draft', 'true')
goto(`/scripts/edit/u/${username}/draft_${uuid}?${params.toString()}`, {
replaceState: true
})
})
</script>
<!--
Never rendered — the sibling +page.ts redirects at the load phase, before
this component can mount. Kept as an empty file because SvelteKit only
registers a route when a +page.svelte exists; without it, /scripts/add
would 404 before the +page.ts ran.
-->
@@ -0,0 +1,27 @@
import { redirect } from '@sveltejs/kit'
import { base } from '$app/paths'
import { get } from 'svelte/store'
import { userStore } from '$lib/stores'
import type { PageLoad } from './$types'
// Redirect at the load phase, before the +page.svelte ever mounts. The
// onMount-then-goto version this replaces painted a blank component for
// 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 uuid = crypto.randomUUID()
const params = new URLSearchParams(url.searchParams)
params.set('new_draft', 'true')
redirect(307, `${base}/scripts/edit/u/${username}/draft_${uuid}?${params.toString()}`)
}