Repository: sveltejs/kit (js)

Issue:
Docs code example sidescroll on mobile
### Describe the bug

When browsing the sveltekit docs on mobile I can not sidescroll the code example blocks.

### Reproduction

Go to https://kit.svelte.dev/docs/accessibility#focus-management on an iphone. Try to sidescroll a code example.

### Logs

_No response_

### System Info

```Shell
Ios 16.1.1
```


### Severity

annoyance

### Additional Information

_No response_

Pull request description:
make `<pre>` elements in docs scrollable
closes #8032 

Patch:
diff --git a/.changeset/light-lamps-crash.md b/.changeset/light-lamps-crash.md
new file mode 100644
index 000000000..2b97de7be
--- /dev/null
+++ b/.changeset/light-lamps-crash.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[breaking] throw an error on invalid load response
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index a5f170dfe..e0c096919 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -675,10 +675,23 @@ export function create_client({ target, base }) {
 				}
 			});
 
-			if (import.meta.env.DEV) {
+			if (__SVELTEKIT_DEV__) {
 				try {
 					lock_fetch();
 					data = (await node.shared.load.call(null, load_input)) ?? null;
+					if (data != null && Object.getPrototypeOf(data) !== Object.prototype) {
+						throw new Error(
+							`a load function related to route '${route.id}' returned ${
+								typeof data !== 'object'
+									? `a ${typeof data}`
+									: data instanceof Response
+									? 'a Response object'
+									: Array.isArray(data)
+									? 'an array'
+									: 'a non-plain object'
+							}, but must return a plain object at the top level (i.e. \`return {...}\`)`
+						);
+					}
 				} finally {
 					unlock_fetch();
 				}
@@ -1300,7 +1313,7 @@ export function create_client({ target, base }) {
 		},
 
 		disable_scroll_handling: () => {
-			if (import.meta.env.DEV && started && !updating) {
+			if (__SVELTEKIT_DEV__ && started && !updating) {
 				throw new Error('Can only disable scroll handling during navigation');
 			}
 
diff --git a/packages/kit/src/runtime/server/data/index.js b/packages/kit/src/runtime/server/data/index.js
index ee10d6d5a..c8d63169d 100644
--- a/packages/kit/src/runtime/server/data/index.js
+++ b/packages/kit/src/runtime/server/data/index.js
@@ -55,6 +55,7 @@ export async function render_data(
 					const node = n == undefined ? n : await options.manifest._.nodes[n]();
 					return load_server_data({
 						event: new_event,
+						options,
 						state,
 						node,
 						parent: async () => {
diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js
index c2665227e..4c3cb4386 100644
--- a/packages/kit/src/runtime/server/page/index.js
+++ b/packages/kit/src/runtime/server/page/index.js
@@ -139,6 +139,7 @@ export async function render_page(event, route, page, options, state, resolve_op
 
 					return await load_server_data({
 						event,
+						options,
 						state,
 						node,
 						parent: async () => {
diff --git a/packages/kit/src/runtime/server/page/load_data.js b/packages/kit/src/runtime/server/page/load_data.js
index 458d1c8bc..1dea122b0 100644
--- a/packages/kit/src/runtime/server/page/load_data.js
+++ b/packages/kit/src/runtime/server/page/load_data.js
@@ -5,13 +5,14 @@ import { unwrap_promises } from '../../../utils/promises.js';
  * Calls the user's server `load` function.
  * @param {{
  *   event: import('types').RequestEvent;
+ *   options: import('types').SSROptions;
  *   state: import('types').SSRState;
  *   node: import('types').SSRNode | undefined;
  *   parent: () => Promise<Record<string, any>>;
  * }} opts
  * @returns {Promise<import('types').ServerDataNode | null>}
  */
-export async function load_server_data({ event, state, node, parent }) {
+export async function load_server_data({ event, options, state, node, parent }) {
 	if (!node?.server) return null;
 
 	const uses = {
@@ -59,6 +60,9 @@ export async function load_server_data({ event, state, node, parent }) {
 	});
 
 	const data = result ? await unwrap_promises(result) : null;
+	if (options.dev) {
+		validate_load_response(data, /** @type {string} */ (event.route.id));
+	}
 
 	return {
 		type: 'data',
@@ -236,9 +240,10 @@ export async function load_data({
 		}
 	});
 
-	const data = await node.shared.load.call(null, load_event);
-
-	return data ? unwrap_promises(data) : null;
+	const result = await node.shared.load.call(null, load_event);
+	const data = result ? await unwrap_promises(result) : null;
+	validate_load_response(data, /** @type {string} */ (event.route.id));
+	return data;
 }
 
 /**
@@ -257,3 +262,23 @@ async function stream_to_string(stream) {
 	}
 	return result;
 }
+
+/**
+ * @param {any} data
+ * @param {string} [routeId]
+ */
+function validate_load_response(data, routeId) {
+	if (data != null && Object.getPrototypeOf(data) !== Object.prototype) {
+		throw new Error(
+			`a load function related to route '${routeId}' returned ${
+				typeof data !== 'object'
+					? `a ${typeof data}`
+					: data instanceof Response
+					? 'a Response object'
+					: Array.isArray(data)
+					? 'an array'
+					: 'a non-plain object'
+			}, but must return a plain object at the top level (i.e. \`return {...}\`)`
+		);
+	}
+}
diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js
index a6582b248..4b5ed2826 100644
--- a/packages/kit/src/runtime/server/page/respond_with_error.js
+++ b/packages/kit/src/runtime/server/page/respond_with_error.js
@@ -40,6 +40,7 @@ export async function respond_with_error({ event, options, state, status, error,
 
 			const server_data_promise = load_server_data({
 				event,
+				options,
 				state,
 				node: default_layout,
 				parent: async () => ({})
diff --git a/sites/kit.svelte.dev/src/lib/docs/server/index.js b/sites/kit.svelte.dev/src/lib/docs/server/index.js
index ec06afa5f..4cd03af5b 100644
--- a/sites/kit.svelte.dev/src/lib/docs/server/index.js
+++ b/sites/kit.svelte.dev/src/lib/docs/server/index.js
@@ -223,7 +223,7 @@ export async function read_file(file) {
 			}
 
 			if (options.file) {
-				html = html.replace('<pre', `<pre data-file="${options.file}"`);
+				html = `<div class="code-block"><span class="filename">${options.file}</span>${html}</div>`;
 			}
 
 			if (version_class) {
diff --git a/sites/site-kit/src/lib/styles/text.css b/sites/site-kit/src/lib/styles/text.css
index cd2f6630f..629a6f8d8 100644
--- a/sites/site-kit/src/lib/styles/text.css
+++ b/sites/site-kit/src/lib/styles/text.css
@@ -32,11 +32,8 @@
 	background: var(--sk-back-4);
 }
 
-.text pre code {
-	padding: 0;
-	margin: 0;
-	top: 0;
-	background: transparent;
+.text .code-block {
+	position: relative;
 }
 
 .text pre {
@@ -44,21 +41,22 @@
 	width: 100%;
 	padding: 1rem;
 	box-shadow: inset 1px 1px 6px hsla(205.7, 63.6%, 30.8%, 0.06);
-}
-
-/** hacky overrides to allow filenames inside code blocks —
-    TODO change the CSS in site-kit so we can get rid of this */
-.text pre {
+	box-sizing: border-box;
 	background-color: var(--code-bg);
 	color: var(--code-base);
-	border-radius: 0.5rem;
-	font-size: 14px;
-	/* max-width: var(--sk-line-max-width); */
-	box-shadow: inset 1px 1px 6px hsla(205.7, 63.6%, 30.8%, 0.06);
-	overflow: hidden;
+	border-radius: var(--sk-border-radius);
+	font-size: var(--sk-text-s);
+	overflow-x: auto;
 }
 
-.text pre[data-file]::before {
+.text pre code {
+	padding: 0;
+	margin: 0;
+	top: 0;
+	background: transparent;
+}
+
+.text .code-block .filename {
 	content: attr(data-file);
 	display: block;
 	width: 100%;
@@ -66,11 +64,15 @@
 	font-size: 1.2rem;
 	font-weight: 400;
 	padding: 1rem 1rem 0.8rem 1rem;
-	margin: -1rem 0 1rem -1rem;
 	color: var(--sk-text-2);
 	background: var(--sk-back-4);
 	border-radius: var(--sk-border-radius) var(--sk-border-radius) 0 0;
-	box-shadow: 0 1px 6px hsla(205.7, 63.6%, 30.8%, 0.06);
+	box-sizing: border-box;
+}
+
+.text .code-block pre {
+	margin-top: 0;
+	border-radius: 0 0 var(--sk-border-radius) var(--sk-border-radius);
 }
 
 .text pre a {


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/errors/invalid-load-response/+page.js b/packages/kit/test/apps/basics/src/routes/errors/invalid-load-response/+page.js
new file mode 100644
index 000000000..464112d1c
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/errors/invalid-load-response/+page.js
@@ -0,0 +1,3 @@
+export function load() {
+	return ['nope'];
+}
diff --git a/packages/kit/test/apps/basics/src/routes/errors/invalid-load-response/+page.svelte b/packages/kit/test/apps/basics/src/routes/errors/invalid-load-response/+page.svelte
new file mode 100644
index 000000000..33c372393
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/errors/invalid-load-response/+page.svelte
@@ -0,0 +1,1 @@
+<!-- this will never be rendered, load function returns something invalid -->
diff --git a/packages/kit/test/apps/basics/src/routes/errors/invalid-server-load-response/+page.server.js b/packages/kit/test/apps/basics/src/routes/errors/invalid-server-load-response/+page.server.js
new file mode 100644
index 000000000..464112d1c
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/errors/invalid-server-load-response/+page.server.js
@@ -0,0 +1,3 @@
+export function load() {
+	return ['nope'];
+}
diff --git a/packages/kit/test/apps/basics/src/routes/errors/invalid-server-load-response/+page.svelte b/packages/kit/test/apps/basics/src/routes/errors/invalid-server-load-response/+page.svelte
new file mode 100644
index 000000000..33c372393
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/errors/invalid-server-load-response/+page.svelte
@@ -0,0 +1,1 @@
+<!-- this will never be rendered, load function returns something invalid -->
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index a3bf7a87b..4e9e5e779 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -421,6 +421,38 @@ test.describe('Errors', () => {
 				'This is your custom error page saying: "Crashing now"'
 			);
 		});
+
+		test('errors on invalid load function response', async ({ page, app, javaScriptEnabled }) => {
+			if (javaScriptEnabled) {
+				await page.goto('/');
+				await app.goto('/errors/invalid-load-response');
+			} else {
+				await page.goto('/errors/invalid-load-response');
+			}
+
+			expect(await page.textContent('footer')).toBe('Custom layout');
+			expect(await page.textContent('#message')).toBe(
+				'This is your custom error page saying: "a load function related to route \'/errors/invalid-load-response\' returned an array, but must return a plain object at the top level (i.e. `return {...}`)"'
+			);
+		});
+
+		test('errors on invalid server load function response', async ({
+			page,
+			app,
+			javaScriptEnabled
+		}) => {
+			if (javaScriptEnabled) {
+				await page.goto('/');
+				await app.goto('/errors/invalid-server-load-response');
+			} else {
+				await page.goto('/errors/invalid-server-load-response');
+			}
+
+			expect(await page.textContent('footer')).toBe('Custom layout');
+			expect(await page.textContent('#message')).toBe(
+				'This is your custom error page saying: "a load function related to route \'/errors/invalid-server-load-response\' returned an array, but must return a plain object at the top level (i.e. `return {...}`)"'
+			);
+		});
 	}
 
 	test('server-side load errors', async ({ page }) => {