From 1ba3e50edc807369d7c649f4ed3fe247ea65831c Mon Sep 17 00:00:00 2001 From: Ottomated Date: Wed, 6 May 2026 15:59:24 -0700 Subject: [PATCH 1/6] test homepage --- .../kit/test/apps/async/src/routes/+page.svelte | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/kit/test/apps/async/src/routes/+page.svelte b/packages/kit/test/apps/async/src/routes/+page.svelte index 8aa2fb04bfbb..0a71dbf7328a 100644 --- a/packages/kit/test/apps/async/src/routes/+page.svelte +++ b/packages/kit/test/apps/async/src/routes/+page.svelte @@ -1 +1,13 @@ -Starting point + + +

Tests

+ From 36a5cf48f90d1666fe819d32bba6b9b787212e9d Mon Sep 17 00:00:00 2001 From: Ottomated Date: Wed, 6 May 2026 16:21:00 -0700 Subject: [PATCH 2/6] failing test --- .../routes/remote/form/as-value/+page.svelte | 31 +-------------- .../routes/remote/form/as-value/Form.svelte | 39 +++++++++++++++++++ 2 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 packages/kit/test/apps/async/src/routes/remote/form/as-value/Form.svelte diff --git a/packages/kit/test/apps/async/src/routes/remote/form/as-value/+page.svelte b/packages/kit/test/apps/async/src/routes/remote/form/as-value/+page.svelte index 996d6b08398b..935999d56171 100644 --- a/packages/kit/test/apps/async/src/routes/remote/form/as-value/+page.svelte +++ b/packages/kit/test/apps/async/src/routes/remote/form/as-value/+page.svelte @@ -1,5 +1,6 @@ @@ -13,27 +14,7 @@
{#each values as value (value.id)} -
- - - - - - - - - - - - -
+
{/each}
@@ -58,12 +39,4 @@ flex-wrap: wrap; gap: 0.25rem; } - .form { - display: flex; - flex-direction: column; - max-width: 200px; - gap: 0.25rem; - padding: 0.5rem; - background: #eee; - } diff --git a/packages/kit/test/apps/async/src/routes/remote/form/as-value/Form.svelte b/packages/kit/test/apps/async/src/routes/remote/form/as-value/Form.svelte new file mode 100644 index 000000000000..c19f30894639 --- /dev/null +++ b/packages/kit/test/apps/async/src/routes/remote/form/as-value/Form.svelte @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + From 7d10edc12b85b544d353b9fd54b825561316bd2d Mon Sep 17 00:00:00 2001 From: Ottomated Date: Wed, 6 May 2026 16:30:07 -0700 Subject: [PATCH 3/6] tick before reset --- .../runtime/client/remote-functions/form.svelte.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/kit/src/runtime/client/remote-functions/form.svelte.js b/packages/kit/src/runtime/client/remote-functions/form.svelte.js index 734209892f4d..5ce43c15b190 100644 --- a/packages/kit/src/runtime/client/remote-functions/form.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/form.svelte.js @@ -482,13 +482,13 @@ export function form(id) { } instance[createAttachmentKey()] = create_attachment( - form_onsubmit(({ submit, form }) => - submit().then((succeeded) => { - if (succeeded) { - form.reset(); - } - }) - ) + form_onsubmit(async ({ submit, form }) => { + const succeeded = await submit(); + if (succeeded) { + await tick(); + form.reset(); + } + }) ); let validate_id = 0; From a0293f5e998d15e30fb84b8eea307d33c3ed437d Mon Sep 17 00:00:00 2001 From: Ottomated Date: Wed, 6 May 2026 16:31:03 -0700 Subject: [PATCH 4/6] changeset --- .changeset/slow-bees-walk.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/slow-bees-walk.md diff --git a/.changeset/slow-bees-walk.md b/.changeset/slow-bees-walk.md new file mode 100644 index 000000000000..1e6433964b1b --- /dev/null +++ b/.changeset/slow-bees-walk.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: wait a tick before resetting forms From 15ad27b11550c12a596cb504fd05f3d381d97b28 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Mon, 11 May 2026 22:33:52 -0700 Subject: [PATCH 5/6] Merge branch 'main' into fix-form-resets --- .changeset/cool-clouds-occur.md | 5 ++++ .changeset/pink-maps-wave.md | 5 ++++ .changeset/shaky-ravens-search.md | 5 ++++ .github/actions/vercel-deploy/action.yml | 2 +- .github/workflows/audit.yml | 2 +- .github/workflows/autofix-lint.yml | 2 +- .github/workflows/ci.yml | 14 +++++----- .github/workflows/release.yml | 2 +- packages/kit/src/exports/public.d.ts | 18 ++++++++----- .../src/runtime/app/server/remote/query.js | 6 +++++ packages/kit/src/runtime/client/client.js | 21 ++++++++------- packages/kit/src/runtime/form-utils.js | 26 ++++++++++++++----- .../routes/remote/form/as-value/Form.svelte | 4 +++ .../remote/form/as-value/form.remote.ts | 10 +++++-- .../[key]/+page.svelte | 12 +++++++++ .../[key]/form.remote.ts | 17 ++++++++++++ .../kit/test/apps/async/test/client.test.js | 11 ++++++++ .../kit/test/apps/async/test/server.test.js | 11 ++++++++ packages/kit/types/index.d.ts | 18 ++++++++----- 19 files changed, 148 insertions(+), 43 deletions(-) create mode 100644 .changeset/cool-clouds-occur.md create mode 100644 .changeset/pink-maps-wave.md create mode 100644 .changeset/shaky-ravens-search.md create mode 100644 packages/kit/test/apps/async/src/routes/remote/form/noop-refresh-non-enhanced/[key]/+page.svelte create mode 100644 packages/kit/test/apps/async/src/routes/remote/form/noop-refresh-non-enhanced/[key]/form.remote.ts diff --git a/.changeset/cool-clouds-occur.md b/.changeset/cool-clouds-occur.md new file mode 100644 index 000000000000..ececc1daf80e --- /dev/null +++ b/.changeset/cool-clouds-occur.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: allow 'submit' and 'hidden' form fields to accept numbers and booleans diff --git a/.changeset/pink-maps-wave.md b/.changeset/pink-maps-wave.md new file mode 100644 index 000000000000..1bc420594666 --- /dev/null +++ b/.changeset/pink-maps-wave.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: abort navigation after async rendering if obsolete diff --git a/.changeset/shaky-ravens-search.md b/.changeset/shaky-ravens-search.md new file mode 100644 index 000000000000..afde5c48b88d --- /dev/null +++ b/.changeset/shaky-ravens-search.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: skip refreshing queries on full-page reload form submissions diff --git a/.github/actions/vercel-deploy/action.yml b/.github/actions/vercel-deploy/action.yml index 1da1f044a652..b522ce224aa2 100644 --- a/.github/actions/vercel-deploy/action.yml +++ b/.github/actions/vercel-deploy/action.yml @@ -24,7 +24,7 @@ outputs: runs: using: 'composite' steps: - - uses: pnpm/action-setup@v6.0.5 + - uses: pnpm/action-setup@v6.0.6 - uses: actions/setup-node@v6 with: diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 16dc116a835b..dc40f94c70fb 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v6.0.5 + - uses: pnpm/action-setup@v6.0.6 - uses: actions/setup-node@v6 with: node-version: '24.x' diff --git a/.github/workflows/autofix-lint.yml b/.github/workflows/autofix-lint.yml index 89fe4303b564..36f7d5e98cb6 100644 --- a/.github/workflows/autofix-lint.yml +++ b/.github/workflows/autofix-lint.yml @@ -50,7 +50,7 @@ jobs: if: github.event_name == 'workflow_dispatch' || steps.pr.outcome == 'success' with: ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || steps.pr.outputs.ref }} - - uses: pnpm/action-setup@v6.0.5 + - uses: pnpm/action-setup@v6.0.6 - uses: actions/setup-node@v6 with: node-version: 24 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 921571a5bfc5..f99a3039be0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v6.0.5 + - uses: pnpm/action-setup@v6.0.6 - uses: actions/setup-node@v6 with: node-version: 24 @@ -52,7 +52,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v6.0.5 + - uses: pnpm/action-setup@v6.0.6 - uses: actions/setup-node@v6 with: node-version: 24 @@ -94,7 +94,7 @@ jobs: steps: - run: git config --global core.autocrlf false - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v6.0.5 + - uses: pnpm/action-setup@v6.0.6 - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} @@ -162,7 +162,7 @@ jobs: steps: - run: git config --global core.autocrlf false - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v6.0.5 + - uses: pnpm/action-setup@v6.0.6 - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} @@ -196,7 +196,7 @@ jobs: steps: - run: git config --global core.autocrlf false - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v6.0.5 + - uses: pnpm/action-setup@v6.0.6 - uses: actions/setup-node@v6 with: node-version: 24 @@ -230,7 +230,7 @@ jobs: steps: - run: git config --global core.autocrlf false - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v6.0.5 + - uses: pnpm/action-setup@v6.0.6 - uses: actions/setup-node@v6 with: node-version: 24 @@ -259,7 +259,7 @@ jobs: node-version: [18, 20, 22, 24] steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v6.0.5 + - uses: pnpm/action-setup@v6.0.6 - uses: actions/setup-node@v6 with: node-version: ${{matrix.node-version}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 41ef0e0ea520..d41ba0d665ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: with: # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - - uses: pnpm/action-setup@v6.0.5 + - uses: pnpm/action-setup@v6.0.6 - name: Setup Node.js uses: actions/setup-node@v6 with: diff --git a/packages/kit/src/exports/public.d.ts b/packages/kit/src/exports/public.d.ts index 104fa7bc9499..1bae4ca70b47 100644 --- a/packages/kit/src/exports/public.d.ts +++ b/packages/kit/src/exports/public.d.ts @@ -1905,8 +1905,8 @@ type InputTypeMap = { checkbox: boolean | string[]; radio: string; file: File; - hidden: string; - submit: string; + hidden: string | number | boolean; + submit: string | number | boolean; button: string; reset: string; image: string; @@ -1997,11 +1997,15 @@ type AsArgs = Type extends 'checkbox' : Value extends boolean ? [type: Type] | [type: Type, value: boolean] : [type: Type] | [type: Type, value: Value | (string & {})] - : Type extends 'radio' | 'submit' | 'hidden' - ? [type: Type, value: Value | (string & {})] - : Type extends 'file' | 'file multiple' - ? [type: Type] - : [type: Type] | [type: Type, value: Value | (string & {})]; + : Type extends 'submit' | 'hidden' + ? Value extends string + ? [type: Type, value: Value | (string & {})] + : [type: Type, value: Value] + : Type extends 'radio' + ? [type: Type, value: Value | (string & {})] + : Type extends 'file' | 'file multiple' + ? [type: Type] + : [type: Type] | [type: Type, value: Value | (string & {})]; /** * Form field accessor type that provides name(), value(), and issues() methods diff --git a/packages/kit/src/runtime/app/server/remote/query.js b/packages/kit/src/runtime/app/server/remote/query.js index 8c81a5a4f95d..97eff7f92d6a 100644 --- a/packages/kit/src/runtime/app/server/remote/query.js +++ b/packages/kit/src/runtime/app/server/remote/query.js @@ -434,6 +434,12 @@ function create_query_resource(__, payload, state, fn) { return false; }, refresh() { + const { event } = get_request_store(); + if (!event.isRemoteRequest) { + // If the form submission is not a remote request, refreshing the data is + // useless, because it can't be returned to the client. + return Promise.resolve(); + } const refresh_context = get_refresh_context(__, 'refresh', payload); const is_immediate_refresh = !refresh_context.cache[refresh_context.payload]; const value = is_immediate_refresh ? get_promise() : fn(); diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index a54dff253bbf..1f4985fb4d26 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -675,7 +675,7 @@ async function initialize(result, target, hydrate) { const style = document.querySelector('style[data-sveltekit]'); if (style) style.remove(); - Object.assign(page, /** @type {import('@sveltejs/kit').Page} */ (result.props.page)); + update(/** @type {import('@sveltejs/kit').Page} */ (result.props.page)); root = new app.root({ target, @@ -1917,6 +1917,17 @@ async function navigate({ await svelte.tick(); await svelte.tick(); + if (token !== nav_token) { + // a new navigation happened while we were waiting for the DOM to update, so abort + nav.reject(new Error('navigation aborted')); + return false; + } + + // Check for async rendering error + if (navigation_result.props.page && rendering_error) { + Object.assign(navigation_result.props.page, rendering_error); + } + // we reset scroll before dealing with focus, to avoid a flash of unscrolled content /** @type {Element | null | ''} */ let deep_linked = null; @@ -1951,14 +1962,6 @@ async function navigate({ autoscroll = true; - if (navigation_result.props.page) { - // Check for async rendering error - if (rendering_error) { - Object.assign(navigation_result.props.page, rendering_error); - } - Object.assign(page, navigation_result.props.page); - } - is_navigating = false; if (type === 'popstate') { diff --git a/packages/kit/src/runtime/form-utils.js b/packages/kit/src/runtime/form-utils.js index df8fc48711dc..42698a207edc 100644 --- a/packages/kit/src/runtime/form-utils.js +++ b/packages/kit/src/runtime/form-utils.js @@ -601,6 +601,23 @@ export function deep_get(object, path) { return current; } +/** + * + * @param {string} field_type + * @param {boolean} is_array + * @param {unknown} input_value + */ +function get_type_prefix(field_type, is_array, input_value) { + if (field_type === 'number' || field_type === 'range') return 'n:'; + if (field_type === 'checkbox' && !is_array) return 'b:'; + if (field_type === 'hidden' || field_type === 'submit') { + const input_type = typeof input_value; + if (input_type === 'number') return 'n:'; + if (input_type === 'boolean') return 'b:'; + } + return ''; +} + /** * Creates a proxy-based field accessor for form data * @param {any} target - Function or empty POJO @@ -674,12 +691,7 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat type === 'select multiple' || (type === 'checkbox' && typeof input_value === 'string'); - const prefix = - type === 'number' || type === 'range' - ? 'n:' - : type === 'checkbox' && !is_array - ? 'b:' - : ''; + const prefix = get_type_prefix(type, is_array, input_value); // Base properties for all input types /** @type {Record} */ @@ -699,7 +711,7 @@ export function create_field_proxy(target, get_input, set_input, get_issues, pat // Handle submit and hidden inputs if (type === 'submit' || type === 'hidden') { if (DEV) { - if (!input_value) { + if (input_value === null || input_value === undefined) { throw new Error(`\`${type}\` inputs must have a value`); } } diff --git a/packages/kit/test/apps/async/src/routes/remote/form/as-value/Form.svelte b/packages/kit/test/apps/async/src/routes/remote/form/as-value/Form.svelte index c19f30894639..e90c61d3bee2 100644 --- a/packages/kit/test/apps/async/src/routes/remote/form/as-value/Form.svelte +++ b/packages/kit/test/apps/async/src/routes/remote/form/as-value/Form.svelte @@ -6,6 +6,10 @@
+ + + + diff --git a/packages/kit/test/apps/async/src/routes/remote/form/as-value/form.remote.ts b/packages/kit/test/apps/async/src/routes/remote/form/as-value/form.remote.ts index 193d842e52da..d910f56ac1e9 100644 --- a/packages/kit/test/apps/async/src/routes/remote/form/as-value/form.remote.ts +++ b/packages/kit/test/apps/async/src/routes/remote/form/as-value/form.remote.ts @@ -8,10 +8,16 @@ const ValueSchema = v.object({ select_field: v.string(), color_field: v.string(), range_field: v.number(), - checkbox_field: v.optional(v.boolean(), false) + checkbox_field: v.optional(v.boolean(), false), + + hidden: v.object({ + string: v.string(), + number: v.number(), + boolean: v.boolean() + }) }); -const default_values: Array> = [ +const default_values: Array, 'hidden'>> = [ { id: '1', text_field: 'Example text', diff --git a/packages/kit/test/apps/async/src/routes/remote/form/noop-refresh-non-enhanced/[key]/+page.svelte b/packages/kit/test/apps/async/src/routes/remote/form/noop-refresh-non-enhanced/[key]/+page.svelte new file mode 100644 index 000000000000..a2f75101922e --- /dev/null +++ b/packages/kit/test/apps/async/src/routes/remote/form/noop-refresh-non-enhanced/[key]/+page.svelte @@ -0,0 +1,12 @@ + + +
Count: {await get_count(params.key)}
+ + + + + diff --git a/packages/kit/test/apps/async/src/routes/remote/form/noop-refresh-non-enhanced/[key]/form.remote.ts b/packages/kit/test/apps/async/src/routes/remote/form/noop-refresh-non-enhanced/[key]/form.remote.ts new file mode 100644 index 000000000000..a2ec98beb41f --- /dev/null +++ b/packages/kit/test/apps/async/src/routes/remote/form/noop-refresh-non-enhanced/[key]/form.remote.ts @@ -0,0 +1,17 @@ +import { form, query } from '$app/server'; +import * as v from 'valibot'; + +let counts = new Map(); + +export const get_count = query(v.string(), (key) => { + return counts.get(key) ?? 0; +}); +export const increment_count = query(v.string(), (key) => { + counts.set(key, (counts.get(key) ?? 0) + 1); + return counts.get(key); +}); + +export const set = form(v.object({ key: v.string() }), async ({ key }) => { + await increment_count(key).refresh(); + await get_count(key).refresh(); +}); diff --git a/packages/kit/test/apps/async/test/client.test.js b/packages/kit/test/apps/async/test/client.test.js index d361d6fcd1ef..c8bf0b60c12a 100644 --- a/packages/kit/test/apps/async/test/client.test.js +++ b/packages/kit/test/apps/async/test/client.test.js @@ -796,6 +796,17 @@ test.describe('remote function mutations', () => { // the value display should also show the updated value await expect(page.locator('#set-value-display')).toHaveText('Set via method'); }); + test('form does refresh queries when a remote request', async ({ page }) => { + await page.goto(`/remote/form/noop-refresh-non-enhanced/${Date.now()}${Math.random()}`); + + const count = page.locator('#count'); + await expect(count).toHaveText('Count: 0'); + + await page.click('button'); + + // Should have refreshed + await expect(count).toHaveText('Count: 1'); + }); }); test.describe('client error boundaries', () => { diff --git a/packages/kit/test/apps/async/test/server.test.js b/packages/kit/test/apps/async/test/server.test.js index 424524ebd262..026b46e7c75c 100644 --- a/packages/kit/test/apps/async/test/server.test.js +++ b/packages/kit/test/apps/async/test/server.test.js @@ -22,4 +22,15 @@ test.describe('remote functions', () => { ); expect(code.includes('const with_read = prerender(')).toBe(false); }); + test("form doesn't refresh queries when not a remote request", async ({ page }) => { + await page.goto(`/remote/form/noop-refresh-non-enhanced/${Date.now()}${Math.random()}`); + + const count = page.locator('#count'); + await expect(count).toHaveText('Count: 0'); + + await page.click('button'); + + // Should not have refreshed + await expect(count).toHaveText('Count: 0'); + }); }); diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 6bfd81192b7b..d28b48b0e601 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1879,8 +1879,8 @@ declare module '@sveltejs/kit' { checkbox: boolean | string[]; radio: string; file: File; - hidden: string; - submit: string; + hidden: string | number | boolean; + submit: string | number | boolean; button: string; reset: string; image: string; @@ -1971,11 +1971,15 @@ declare module '@sveltejs/kit' { : Value extends boolean ? [type: Type] | [type: Type, value: boolean] : [type: Type] | [type: Type, value: Value | (string & {})] - : Type extends 'radio' | 'submit' | 'hidden' - ? [type: Type, value: Value | (string & {})] - : Type extends 'file' | 'file multiple' - ? [type: Type] - : [type: Type] | [type: Type, value: Value | (string & {})]; + : Type extends 'submit' | 'hidden' + ? Value extends string + ? [type: Type, value: Value | (string & {})] + : [type: Type, value: Value] + : Type extends 'radio' + ? [type: Type, value: Value | (string & {})] + : Type extends 'file' | 'file multiple' + ? [type: Type] + : [type: Type] | [type: Type, value: Value | (string & {})]; /** * Form field accessor type that provides name(), value(), and issues() methods From def68252892bab2e726ad8b5add89e52a10fcad2 Mon Sep 17 00:00:00 2001 From: Ottomated Date: Wed, 20 May 2026 16:23:24 -0700 Subject: [PATCH 6/6] rm --- .ottotime | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .ottotime diff --git a/.ottotime b/.ottotime deleted file mode 100644 index b2369ad949fb..000000000000 --- a/.ottotime +++ /dev/null @@ -1,2 +0,0 @@ -# OTTOTIME -# Do not edit manually. Check into git.