From 4f65efae79cccbea992649d86ec35d686469d1cd Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 11 Oct 2024 15:49:53 -0400 Subject: [PATCH 01/12] nicer diff highlighting --- .../site-kit/src/lib/components/Text.svelte | 19 ++++++++++ .../site-kit/src/lib/markdown/renderer.ts | 36 ++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/site-kit/src/lib/components/Text.svelte b/packages/site-kit/src/lib/components/Text.svelte index c42752c328..5dcb68263b 100644 --- a/packages/site-kit/src/lib/components/Text.svelte +++ b/packages/site-kit/src/lib/components/Text.svelte @@ -303,6 +303,25 @@ &.language-diff code { color: var(--sk-code-diff-base); } + + .highlight { + --color: rgba(220, 220, 0, 0.2); + background: var(--color); + outline: 2px solid var(--color); + border-radius: 2px; + + &.add { + --color: rgba(0, 255, 0, 0.18); + } + + &.remove { + --color: rgba(255, 0, 0, 0.1); + + :root.dark & { + --color: rgba(255, 0, 0, 0.27); + } + } + } } } diff --git a/packages/site-kit/src/lib/markdown/renderer.ts b/packages/site-kit/src/lib/markdown/renderer.ts index c166e3d295..148184e7c0 100644 --- a/packages/site-kit/src/lib/markdown/renderer.ts +++ b/packages/site-kit/src/lib/markdown/renderer.ts @@ -7,7 +7,6 @@ import * as prettier from 'prettier'; import { codeToHtml, createCssVariablesTheme } from 'shiki'; import { transformerTwoslash } from '@shikijs/twoslash'; import { SHIKI_LANGUAGE_MAP, slugify, smart_quotes, transform } from './utils'; -import type { Modules } from './index'; import { fileURLToPath } from 'node:url'; interface SnippetOptions { @@ -611,6 +610,19 @@ function replace_blank_lines(html: string) { return html.replaceAll(/
( )?<\/div>/g, '
\n
'); } +const delimiter_substitutes = { + '+++': ' ', + '---': ' ', + ':::': ' ' +}; + +function highlight_spans(content: string, classname: string) { + return content + .split('\n') + .map((line) => `${line}`) + .join('\n'); +} + async function syntax_highlight({ source, filename, @@ -626,6 +638,13 @@ async function syntax_highlight({ }) { let html = ''; + source = source.replace( + /(\+\+\+|---|:::)/g, + (_, delimiter: keyof typeof delimiter_substitutes) => { + return delimiter_substitutes[delimiter]; + } + ); + if (/^(dts|yaml|yml)/.test(language)) { html = replace_blank_lines( await codeToHtml(source, { @@ -699,6 +718,21 @@ async function syntax_highlight({ html = replace_blank_lines(highlighted); } + // munge shiki output: put whitespace outside `` elements, so that + // highlight delimiters fall outside tokens + html = html.replace(/()(\s+)/g, '$2$1').replace(/(\s+)(<\/span>)/g, '$2$1'); + + html = html + .replace(/ {13}([^ ][^]+?) {13}/g, (_, content) => { + return highlight_spans(content, 'highlight add'); + }) + .replace(/ {11}([^ ][^]+?) {11}/g, (_, content) => { + return highlight_spans(content, 'highlight remove'); + }) + .replace(/ {9}([^ ][^]+?) {9}/g, (_, content) => { + return highlight_spans(content, 'highlight'); + }); + return indent_multiline_comments(html) .replace(/\/\*…\*\//g, '…') .replace(' Date: Fri, 11 Oct 2024 17:19:14 -0400 Subject: [PATCH 02/12] fixes --- .../site-kit/src/lib/markdown/renderer.ts | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/packages/site-kit/src/lib/markdown/renderer.ts b/packages/site-kit/src/lib/markdown/renderer.ts index 148184e7c0..a40648affd 100644 --- a/packages/site-kit/src/lib/markdown/renderer.ts +++ b/packages/site-kit/src/lib/markdown/renderer.ts @@ -203,6 +203,17 @@ export async function render_content_markdown( let { source, options } = parse_options(token.text, token.lang); source = adjust_tab_indentation(source, token.lang); + const match = /((?:[\s\S]+)\/\/ ---cut---\n)?([\s\S]+)/.exec(source)!; + + const prelude = match[1]; + + source = match[2].replace( + /(\+\+\+|---|:::)/g, + (_, delimiter: keyof typeof delimiter_substitutes) => { + return delimiter_substitutes[delimiter]; + } + ); + const converted = token.lang === 'js' || token.lang === 'svelte' ? await generate_ts_from_js(source, token.lang, options) @@ -228,6 +239,7 @@ export async function render_content_markdown( html += '
'; html += await syntax_highlight({ + prelude, filename, language: token.lang, source, @@ -237,6 +249,7 @@ export async function render_content_markdown( if (converted) { html += await syntax_highlight({ + prelude, filename, language: token.lang === 'js' ? 'ts' : token.lang, source: converted, @@ -315,7 +328,16 @@ async function generate_ts_from_js( // config files have no .ts equivalent if (options.file === 'svelte.config.js') return; - return await convert_to_ts(code.replace(/\/\/\/ file: .+?\n/, '')); + let [before, after] = code.split('// ---cut---\n'); + + if (!after) { + after = before; + before = ''; + } + + const converted = await convert_to_ts(after.replace(/\/\/\/ file: .+?\n/, '')); + + return converted && [before, converted].join('// ---cut---\n'); } // Assumption: no module blocks @@ -472,20 +494,7 @@ async function convert_to_ts(js_code: string, indent = '', offset = '') { code.appendLeft(insertion_point, offset + import_statements + '\n'); } - let transformed = await prettier.format(code.toString(), { - printWidth: 100, - parser: 'typescript', - useTabs: true, - singleQuote: true, - trailingComma: 'none' - }); - - // Indent transformed's each line by 2 - transformed = transformed - .replace(/\n$/, '') - .split('\n') - .map((line) => indent + line) - .join('\n'); + let transformed = code.toString(); return transformed === js_code ? undefined : transformed.replace(/\n\s*\n\s*\n/g, '\n\n'); @@ -624,6 +633,7 @@ function highlight_spans(content: string, classname: string) { } async function syntax_highlight({ + prelude, source, filename, language, @@ -638,13 +648,6 @@ async function syntax_highlight({ }) { let html = ''; - source = source.replace( - /(\+\+\+|---|:::)/g, - (_, delimiter: keyof typeof delimiter_substitutes) => { - return delimiter_substitutes[delimiter]; - } - ); - if (/^(dts|yaml|yml)/.test(language)) { html = replace_blank_lines( await codeToHtml(source, { @@ -657,7 +660,7 @@ async function syntax_highlight({ let banner = twoslashBanner?.(filename, source, language, options); if (banner) { - banner = '// @filename: injected.d.ts\n' + banner; + banner = '// @filename: injected.d.ts\n' + banner + '\n' + prelude; if (source.includes('// @filename:')) { source = source.replace('// @filename:', `${banner}\n\n// @filename:`); From e003d2d7c5aab40884418e0edb1e6760e2fa48ac Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 11 Oct 2024 18:24:09 -0400 Subject: [PATCH 03/12] fix --- .../site-kit/src/lib/markdown/renderer.ts | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/site-kit/src/lib/markdown/renderer.ts b/packages/site-kit/src/lib/markdown/renderer.ts index a40648affd..883ee5e72f 100644 --- a/packages/site-kit/src/lib/markdown/renderer.ts +++ b/packages/site-kit/src/lib/markdown/renderer.ts @@ -349,7 +349,7 @@ async function generate_ts_from_js( if (!ts) return; - return code.replace(outer, ``); + return code.replace(outer, ``); } function get_jsdoc(node: ts.Node) { @@ -368,6 +368,15 @@ async function convert_to_ts(js_code: string, indent = '', offset = '') { // *\/ appears in some JsDoc comments in d.ts files due to the JSDoc-in-JSDoc problem .replace(/\*\\\//g, '*/'); + // TODO temp + if (js_code.includes('// ---cut---')) { + throw new Error('unexpected cut directive'); + } + + if (js_code.includes('/// file:')) { + throw new Error('unexpected file directive'); + } + const ast = ts.createSourceFile( 'filename.ts', js_code, @@ -411,7 +420,7 @@ async function convert_to_ts(js_code: string, indent = '', offset = '') { ); code.appendLeft(node.body.getStart(), '=> '); - code.appendLeft(node.body.getEnd(), ')'); + code.appendLeft(node.body.getEnd(), ');'); modified = true; } @@ -479,19 +488,20 @@ async function convert_to_ts(js_code: string, indent = '', offset = '') { return `${indent}import type { ${Array.from(names).join(', ')} } from '${from}';`; }) .join('\n'); - const idxOfLastImport = [...ast.statements] - .reverse() - .find((statement) => ts.isImportDeclaration(statement)) - ?.getEnd(); - const insertion_point = Math.max( - idxOfLastImport ? idxOfLastImport + 1 : 0, - js_code.includes('---cut---') - ? js_code.indexOf('\n', js_code.indexOf('---cut---')) + 1 - : js_code.includes('/// file:') - ? js_code.indexOf('\n', js_code.indexOf('/// file:')) + 1 - : 0 + + const last_import = [...ast.statements].findLast((statement) => + ts.isImportDeclaration(statement) ); - code.appendLeft(insertion_point, offset + import_statements + '\n'); + + if (last_import) { + let i = last_import.getEnd(); + while (js_code[i] !== '\n') i += 1; + i += 1; + + code.appendLeft(i, import_statements + '\n'); + } else { + code.prependLeft(0, offset + import_statements + '\n'); + } } let transformed = code.toString(); @@ -640,6 +650,7 @@ async function syntax_highlight({ twoslashBanner, options }: { + prelude: string; source: string; filename: string; language: string; From 02cff39ec080b0eacaf9a0ae032003032e7c28ad Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 11 Oct 2024 19:22:07 -0400 Subject: [PATCH 04/12] fixes --- .../site-kit/src/lib/markdown/renderer.ts | 20 ++++++++++++++----- .../src/lib/styles/utils/twoslash.css | 5 +++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/site-kit/src/lib/markdown/renderer.ts b/packages/site-kit/src/lib/markdown/renderer.ts index 883ee5e72f..8eb793b782 100644 --- a/packages/site-kit/src/lib/markdown/renderer.ts +++ b/packages/site-kit/src/lib/markdown/renderer.ts @@ -630,8 +630,8 @@ function replace_blank_lines(html: string) { } const delimiter_substitutes = { - '+++': ' ', - '---': ' ', + '---': ' ', + '+++': ' ', ':::': ' ' }; @@ -683,7 +683,15 @@ async function syntax_highlight({ } } - html = await codeToHtml(source, { + /** We need to stash code wrapped in `---` highlights, because otherwise TS will error on e.g. bad syntax, duplicate declarations */ + const redactions: string[] = []; + + const redacted = source.replace(/( {13}(?:[^ ][^]+?) {13})/g, (_, content) => { + redactions.push(content); + return ' '.repeat(content.length); + }); + + html = await codeToHtml(redacted, { lang: 'ts', theme, transformers: [ @@ -696,6 +704,8 @@ async function syntax_highlight({ }) ] }); + + html = html.replace(/ {27,}/g, () => redactions.shift()!); } catch (e) { console.error((e as Error).message); console.warn(source); @@ -738,10 +748,10 @@ async function syntax_highlight({ html = html .replace(/ {13}([^ ][^]+?) {13}/g, (_, content) => { - return highlight_spans(content, 'highlight add'); + return highlight_spans(content, 'highlight remove'); }) .replace(/ {11}([^ ][^]+?) {11}/g, (_, content) => { - return highlight_spans(content, 'highlight remove'); + return highlight_spans(content, 'highlight add'); }) .replace(/ {9}([^ ][^]+?) {9}/g, (_, content) => { return highlight_spans(content, 'highlight'); diff --git a/packages/site-kit/src/lib/styles/utils/twoslash.css b/packages/site-kit/src/lib/styles/utils/twoslash.css index cee75a3e5f..2fefa12605 100644 --- a/packages/site-kit/src/lib/styles/utils/twoslash.css +++ b/packages/site-kit/src/lib/styles/utils/twoslash.css @@ -5,3 +5,8 @@ display: none; } } + +.twoslash-error-line { + visibility: hidden; + height: 0; +} From 421f80585949c10defebb688eed86ab1a78c3061 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 11 Oct 2024 21:41:07 -0400 Subject: [PATCH 05/12] fix --- .../site-kit/src/lib/markdown/renderer.ts | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/packages/site-kit/src/lib/markdown/renderer.ts b/packages/site-kit/src/lib/markdown/renderer.ts index 8eb793b782..a5653cb456 100644 --- a/packages/site-kit/src/lib/markdown/renderer.ts +++ b/packages/site-kit/src/lib/markdown/renderer.ts @@ -666,32 +666,28 @@ async function syntax_highlight({ theme }) ); - } else if (/^(js|ts)$/.test(language)) { - try { - let banner = twoslashBanner?.(filename, source, language, options); - - if (banner) { - banner = '// @filename: injected.d.ts\n' + banner + '\n' + prelude; - - if (source.includes('// @filename:')) { - source = source.replace('// @filename:', `${banner}\n\n// @filename:`); - } else { - source = source.replace( - /^(?!\/\/ @)/m, - `${banner}\n\n// @filename: index.${language}\n// ---cut---\n` - ); - } - } + } else if (language === 'js' || language === 'ts') { + let banner = twoslashBanner?.(filename, source, language, options); - /** We need to stash code wrapped in `---` highlights, because otherwise TS will error on e.g. bad syntax, duplicate declarations */ - const redactions: string[] = []; + if (banner) { + banner = '// @filename: injected.d.ts\n' + banner; + } - const redacted = source.replace(/( {13}(?:[^ ][^]+?) {13})/g, (_, content) => { - redactions.push(content); - return ' '.repeat(content.length); - }); + prelude = (banner ?? '') + '\n' + (prelude ?? '// ---cut---\n'); - html = await codeToHtml(redacted, { + if (language === 'ts') { + prelude = prelude.replace(/(\/\/ @filename: .+)\.js$/gm, '$1.ts'); + } + + /** We need to stash code wrapped in `---` highlights, because otherwise TS will error on e.g. bad syntax, duplicate declarations */ + const redactions: string[] = []; + + const redacted = source.replace(/( {13}(?:[^ ][^]+?) {13})/g, (_, content) => { + redactions.push(content); + return ' '.repeat(content.length); + }); + try { + html = await codeToHtml(prelude + redacted, { lang: 'ts', theme, transformers: [ @@ -708,7 +704,7 @@ async function syntax_highlight({ html = html.replace(/ {27,}/g, () => redactions.shift()!); } catch (e) { console.error((e as Error).message); - console.warn(source); + console.warn(prelude + redacted); throw new Error(`Error compiling snippet in ${filename}`); } From 514bf1177d2f612163662c85fbe72490ca5a7fef Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 11 Oct 2024 22:19:24 -0400 Subject: [PATCH 06/12] fix --- packages/site-kit/src/lib/markdown/renderer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/site-kit/src/lib/markdown/renderer.ts b/packages/site-kit/src/lib/markdown/renderer.ts index a5653cb456..468de45edb 100644 --- a/packages/site-kit/src/lib/markdown/renderer.ts +++ b/packages/site-kit/src/lib/markdown/renderer.ts @@ -431,7 +431,9 @@ async function convert_to_ts(js_code: string, indent = '', offset = '') { const variable_statement = node.declarationList.declarations[0]; if (variable_statement.name.getText() === 'actions') { - code.appendLeft(variable_statement.getEnd(), ` satisfies ${name}`); + let i = variable_statement.getEnd(); + while (code.original[i - 1] !== '}') i -= 1; + code.appendLeft(i, ` satisfies ${name}`); } else { code.appendLeft( variable_statement.name.getEnd(), From c3f812a6a50237f15df79a230dff8604302f8cef Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 11 Oct 2024 22:32:40 -0400 Subject: [PATCH 07/12] sync --- .../blog/2023-08-31-view-transitions.md | 4 +- .../docs/kit/20-core-concepts/20-load.md | 31 +++-- .../kit/20-core-concepts/30-form-actions.md | 113 ++++++++++-------- .../20-core-concepts/50-state-management.md | 15 +-- .../10-building-your-app.md | 8 +- .../25-build-and-deploy/40-adapter-node.md | 10 +- .../25-build-and-deploy/50-adapter-static.md | 6 +- .../60-adapter-cloudflare.md | 10 +- .../70-adapter-cloudflare-workers.md | 10 +- .../kit/30-advanced/10-advanced-routing.md | 25 ++-- .../content/docs/kit/30-advanced/25-errors.md | 6 +- .../docs/kit/30-advanced/70-packaging.md | 32 ++--- .../docs/kit/40-best-practices/07-images.md | 6 +- .../30-migrating-to-sveltekit-2.md | 20 ++-- .../docs/kit/98-reference/10-@sveltejs-kit.md | 4 +- .../docs/kit/98-reference/50-configuration.md | 4 +- .../content/docs/kit/98-reference/54-types.md | 2 +- .../docs/svelte/04-runtime/02-context.md | 3 +- .../svelte/04-runtime/03-lifecycle-hooks.md | 34 +++--- .../content/docs/svelte/05-misc/02-testing.md | 6 +- .../.generated/compile-warnings.md | 21 ++-- .../98-reference/30-compiler-warnings.md | 21 ++-- 22 files changed, 204 insertions(+), 187 deletions(-) diff --git a/apps/svelte.dev/content/blog/2023-08-31-view-transitions.md b/apps/svelte.dev/content/blog/2023-08-31-view-transitions.md index 1d934dd1f4..6e1fc58a10 100644 --- a/apps/svelte.dev/content/blog/2023-08-31-view-transitions.md +++ b/apps/svelte.dev/content/blog/2023-08-31-view-transitions.md @@ -14,7 +14,7 @@ However, until now, you couldn’t easily use this API in a SvelteKit app, since You can trigger a view transition by calling `document.startViewTransition` and passing a callback that updates the DOM somehow. For our purposes today, SvelteKit will update the DOM as the user navigates. Once the callback finishes, the browser will transition to the new page state — by default, it does a crossfade between the old and the new states. ```js -// @errors: 2339 +// @errors: 2339 2304 const domUpdate = async () => {}; // ---cut--- document.startViewTransition(async () => { @@ -162,7 +162,7 @@ Now, the header will not transition in and out on navigation, but the rest of th > [!DETAILS] Fixing the types > Since `startViewTransition` is not supported by all browsers, your IDE may not know that it exists. To make the errors go away and get the correct typings, add the following to your `app.d.ts`: > -> ```ts +> ```dts > declare global { > // preserve any customizations you have here > namespace App { diff --git a/apps/svelte.dev/content/docs/kit/20-core-concepts/20-load.md b/apps/svelte.dev/content/docs/kit/20-core-concepts/20-load.md index 46d8deec2f..4d1959e557 100644 --- a/apps/svelte.dev/content/docs/kit/20-core-concepts/20-load.md +++ b/apps/svelte.dev/content/docs/kit/20-core-concepts/20-load.md @@ -110,26 +110,26 @@ export async function load() { Data returned from layout `load` functions is available to child `+layout.svelte` components and the `+page.svelte` component as well as the layout that it 'belongs' to. -```diff +```svelte /// file: src/routes/blog/[slug]/+page.svelte

{data.post.title}

{@html data.post.content}
-+{#if next} -+

Next post: {next.title}

-+{/if} ++++{#if next} +

Next post: {next.title}

+{/if}+++ ``` > [!NOTE] If multiple `load` functions return data with the same key, the last one 'wins' — the result of a layout `load` returning `{ a: 1, b: 2 }` and a page `load` returning `{ b: 3, c: 4 }` would be `{ a: 1, b: 3, c: 4 }`. @@ -381,16 +381,21 @@ In `+page.js` or `+layout.js` it will return data from parent `+layout.js` files Take care not to introduce waterfalls when using `await parent()`. Here, for example, `getData(params)` does not depend on the result of calling `parent()`, so we should call it first to avoid a delayed render. -```diff +```js /// file: +page.js +// @filename: ambient.d.ts +declare function getData(params: Record): Promise<{ meta: any }> + +// @filename: index.js +// ---cut--- /** @type {import('./$types').PageLoad} */ export async function load({ params, parent }) { -- const parentData = await parent(); + ---const parentData = await parent();--- const data = await getData(params); -+ const parentData = await parent(); + +++const parentData = await parent();+++ return { - ...data + ...data, meta: { ...parentData.meta, ...data.meta } }; } diff --git a/apps/svelte.dev/content/docs/kit/20-core-concepts/30-form-actions.md b/apps/svelte.dev/content/docs/kit/20-core-concepts/30-form-actions.md index b5bb6ea5cf..ce23f7c43d 100644 --- a/apps/svelte.dev/content/docs/kit/20-core-concepts/30-form-actions.md +++ b/apps/svelte.dev/content/docs/kit/20-core-concepts/30-form-actions.md @@ -54,17 +54,17 @@ We can also invoke the action from other pages (for example if there's a login w Instead of one `default` action, a page can have as many named actions as it needs: -```diff +```js /// file: src/routes/login/+page.server.js /** @type {import('./$types').Actions} */ export const actions = { -- default: async (event) => { -+ login: async (event) => { +--- default: async (event) => {--- ++++ login: async (event) => {+++ // TODO log the user in }, -+ register: async (event) => { -+ // TODO register the user -+ } ++++ register: async (event) => { + // TODO register the user + }+++ }; ``` @@ -82,10 +82,9 @@ To invoke a named action, add a query parameter with the name prefixed by a `/` As well as the `action` attribute, we can use the `formaction` attribute on a button to `POST` the same form data to a different action than the parent `
`: -```diff +```svelte /// file: src/routes/login/+page.svelte -- -+ + -+ + ++++++
``` @@ -106,8 +105,14 @@ As well as the `action` attribute, we can use the `formaction` attribute on a bu Each action receives a `RequestEvent` object, allowing you to read the data with `request.formData()`. After processing the request (for example, logging the user in by setting a cookie), the action can respond with data that will be available through the `form` property on the corresponding page and through `$page.form` app-wide until the next update. ```js -// @errors: 2304 /// file: src/routes/login/+page.server.js +// @filename: ambient.d.ts +declare module '$lib/server/db'; + +// @filename: index.js +// ---cut--- +import * as db from '$lib/server/db'; + /** @type {import('./$types').PageServerLoad} */ export async function load({ cookies }) { const user = await db.getUserFromSession(cookies.get('sessionid')); @@ -153,9 +158,15 @@ export const actions = { If the request couldn't be processed because of invalid data, you can return validation errors — along with the previously submitted form values — back to the user so that they can try again. The `fail` function lets you return an HTTP status code (typically 400 or 422, in the case of validation errors) along with the data. The status code is available through `$page.status` and the data through `form`: -```diff +```js /// file: src/routes/login/+page.server.js -+import { fail } from '@sveltejs/kit'; +// @filename: ambient.d.ts +declare module '$lib/server/db'; + +// @filename: index.js +// ---cut--- ++++import { fail } from '@sveltejs/kit';+++ +import * as db from '$lib/server/db'; /** @type {import('./$types').Actions} */ export const actions = { @@ -164,15 +175,15 @@ export const actions = { const email = data.get('email'); const password = data.get('password'); -+ if (!email) { -+ return fail(400, { email, missing: true }); -+ } ++++ if (!email) { + return fail(400, { email, missing: true }); + }+++ const user = await db.getUser(email); -+ if (!user || user.password !== hash(password)) { -+ return fail(400, { email, incorrect: true }); -+ } ++++ if (!user || user.password !== db.hash(password)) { + return fail(400, { email, incorrect: true }); + }+++ cookies.set('sessionid', await db.createSession(user), { path: '/' }); @@ -186,15 +197,14 @@ export const actions = { > [!NOTE] Note that as a precaution, we only return the email back to the page — not the password. -```diff +```svelte /// file: src/routes/login/+page.svelte
-+ {#if form?.missing}

The email field is required

{/if} -+ {#if form?.incorrect}

Invalid credentials!

{/if} ++++ {#if form?.missing}

The email field is required

{/if} + {#if form?.incorrect}

Invalid credentials!

{/if}+++