feat(frontend): load URL-encoded scripts on /scripts/edit + consume-once

Mirror the URL-hash seed mechanism from /scripts/add to /scripts/edit
for parity: decode the base64-JSON-encoded NewScript payload from the
URL hash, apply it over the bakedBaseline as the editor's initial
state, send "Loaded from URL", and strip the hash immediately via
window.history.replaceState so a reload restores the user's autosave
rather than re-injecting the seed.

The seed wins over local autosave + backend draft + deployed —
UserDraft.remove(script, draftPath) drops the stale autosave on disk
before setDraftAndMeta writes the seeded value, so the user's
subsequent edits will overwrite cleanly.

Skipped when ?hash= is in the URL (historical-version view, which is
read-only relative to drafts) and when the hash fragment isn't a
parseable encoded payload.

No callers build /scripts/edit#<encoded> URLs today — this lands the
mechanism for future symmetry with /scripts/add.
This commit is contained in:
Diego Imbert
2026-05-13 19:32:54 +02:00
parent 13c0237a81
commit 3c3bd54148
@@ -34,6 +34,28 @@
const draftPath = hash ? '' : (page.params.path ?? '')
const scriptHandle = UserDraft.use<EditableScript>('script', draftPath)
/** Some pages base64-JSON-encode a NewScript-like payload into the URL
* hash on `/scripts/edit/<path>#…`. Treat it as a one-shot seed that
* wins over local autosave + backend draft + deployed: apply, toast,
* strip from the URL. Same logic as /scripts/add, kept in this file for
* a faithful mirror of its decoder.
*
* Can't reuse `decodeState` from utils.ts — it fires its own error toast
* on parse failure, which would noise up the UI for unrelated anchors.
*/
function decodeUrlScriptSeed(): Partial<EditableScript> | undefined {
const fragment = page.url.hash.startsWith('#') ? page.url.hash.slice(1) : ''
if (!fragment) return undefined
try {
const decoded = JSON.parse(decodeURIComponent(atob(fragment)))
if (decoded && typeof decoded === 'object') return decoded as Partial<EditableScript>
} catch {
// Hash isn't a valid encoded script — ignore.
}
return undefined
}
let urlScriptSeed = decodeUrlScriptSeed()
let initialPath: string = $state('')
let scriptBuilder: ScriptBuilder | undefined = $state(undefined)
@@ -124,7 +146,24 @@
parent_hash: topHash ?? scriptWithDraft.hash
}
if (localDraft != undefined) {
if (urlScriptSeed) {
// URL hash seed wins over local autosave + backend draft + deployed.
// Wipe the stale autosave on disk so the in-memory seed becomes the
// only source — the user's subsequent edits will overwrite cleanly.
const seeded = { ...bakedBaseline, ...urlScriptSeed } as EditableScript
UserDraft.remove('script', draftPath)
scriptHandle.setDraftAndMeta(seeded, newRevs)
sendUserToast('Loaded from URL')
// Consume-once: strip the hash so a reload restores the user's
// autosave (which now reflects the seed + edits) instead of
// re-applying the seed.
if (typeof window !== 'undefined') {
const url = new URL(window.location.href)
url.hash = ''
window.history.replaceState(window.history.state, '', url.toString())
}
urlScriptSeed = undefined
} else if (localDraft != undefined) {
const reference = backendDraft ?? scriptWithDraft
const referenceClean = cleanValueProperties(reference)
const localClean = cleanValueProperties(localDraft)