fix(sessions): hand back only what a form actually held

A tab closed while the deployed item was still being fetched handed the card an
empty object, wiping the proposal it was still parked on: the page registers its
destroy hook long before `RunForm` gives `args` a value.

The comment above it also claimed every way out destroys the page. The next call
re-pointing the same tab does not, and that page's edits stop there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-10-07 08:19:38 -07:00
co-authored by Claude Opus 5
parent 8fb8d2a329
commit b824fe3ffe
2 changed files with 24 additions and 8 deletions
@@ -390,10 +390,18 @@
// The form here is a copy, so what the reader typed into it would be lost when the call
// goes back to the card — the card would then offer, and run, the arguments this page
// opened with. Written on the way out rather than per keystroke: every way the page stops
// carrying a call and still holds edits destroys it, since the tab either closes or
// changes url, and `loadKey` remounts on that.
onDestroy(() => pendingRun?.handBack(args ?? {}))
// opened with. Written on the way out rather than per keystroke.
//
// Destroy covers the ways out that carry edits: the tab closes, or its url changes and
// `loadKey` remounts. Not the next call re-pointing this same tab, which swaps the form
// under a page that stays mounted; that page's edits are its own and go no further.
//
// Only once a form has held them. `args` is undefined until `RunForm` initialises, so a
// tab closed while the item is still being fetched would otherwise hand the card an empty
// object and wipe the proposal it is still parked on.
onDestroy(() => {
if (args !== undefined) pendingRun?.handBack(args)
})
// The dev workspace's editor is not one the session panel can host, so from a preview tab
// it opens in a new browser tab, as the session editors' own entry does.
@@ -439,10 +439,18 @@
// The form here is a copy, so what the reader typed into it would be lost when the call
// goes back to the card — the card would then offer, and run, the arguments this page
// opened with. Written on the way out rather than per keystroke: every way the page stops
// carrying a call and still holds edits destroys it, since the tab either closes or
// changes url, and `loadKey` remounts on that.
onDestroy(() => pendingRun?.handBack(args ?? {}))
// opened with. Written on the way out rather than per keystroke.
//
// Destroy covers the ways out that carry edits: the tab closes, or its url changes and
// `loadKey` remounts. Not the next call re-pointing this same tab, which swaps the form
// under a page that stays mounted; that page's edits are its own and go no further.
//
// Only once a form has held them. `args` is undefined until `RunForm` initialises, so a
// tab closed while the item is still being fetched would otherwise hand the card an empty
// object and wipe the proposal it is still parked on.
onDestroy(() => {
if (args !== undefined) pendingRun?.handBack(args)
})
// Read once on purpose: these args seed the form, so tracking the fragment would
// overwrite what the user has typed whenever it changes.