sv 0.16.6 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { _ as name, a as detectPackageManager, d as Command, g as helpConfig, h as forwardExitCode, l as from, n as add, r as create, u as program, v as version } from "./engine-DbEL5s1q.mjs";
2
+ import { _ as name, a as detectPackageManager, d as Command, g as helpConfig, h as forwardExitCode, l as from, n as add, r as create, u as program, v as version } from "./engine-B7chsCPG.mjs";
3
3
  import { color, resolveCommandArray } from "@sveltejs/sv-utils";
4
4
  import V from "node:process";
5
5
  import { execSync } from "node:child_process";
@@ -13,7 +13,7 @@ type OfficialAddons = {
13
13
  mdsvex: Addon<any>;
14
14
  paraglide: Addon<any>;
15
15
  storybook: Addon<any>;
16
- mcp: Addon<any>;
16
+ aiTools: Addon<any>;
17
17
  experimental: Addon<any>;
18
18
  };
19
19
  declare const officialAddons: OfficialAddons;
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from "node:module";
2
- import { AGENTS, COMMANDS, Walker, coerceVersion, color, constructCommand, createPrinter, dedent, defineEnv, detect, downloadJson, fileExists, isVersionUnsupportedBelow, js, loadFile, loadPackageJson, minVersion, parse, pnpm, resolveCommand, resolveCommandArray, sanitizeName, saveFile, svelte, svelteConfig, transforms } from "@sveltejs/sv-utils";
2
+ import { AGENTS, COMMANDS, KIT3_TSCONFIG, KIT3_TSCONFIG_DEFAULT, Walker, coerceVersion, color, constructCommand, createPrinter, dedent, defineEnv, detect, downloadJson, fileExists, isKit3, isVersionUnsupportedBelow, js, libSubpathImports, loadFile, loadPackageJson, minVersion, parse, pnpm, resolveCommand, resolveCommandArray, resolveLibPrefix, sanitizeName, saveFile, svelte, svelteConfig, transforms } from "@sveltejs/sv-utils";
3
3
  import fs, { closeSync, existsSync, openSync, readSync, statSync } from "node:fs";
4
4
  import path, { basename, delimiter, dirname, isAbsolute, join, normalize, resolve } from "node:path";
5
5
  import { promisify, stripVTControlCharacters, styleText } from "node:util";
@@ -1348,7 +1348,7 @@ ${i ? styleText("cyan", x$1) : ""}
1348
1348
  //#endregion
1349
1349
  //#region package.json
1350
1350
  var name = "sv";
1351
- var version = "0.16.6";
1351
+ var version = "0.17.0";
1352
1352
  //#endregion
1353
1353
  //#region src/core/errors.ts
1354
1354
  var UnsupportedError = class extends Error {
@@ -1361,6 +1361,7 @@ var UnsupportedError = class extends Error {
1361
1361
  };
1362
1362
  //#endregion
1363
1363
  //#region src/core/common.ts
1364
+ const RX_IMPORT_ONLY = /^\s*@\S+\s*$/;
1364
1365
  const NO_PREFIX = "--no-";
1365
1366
  let options$8 = [];
1366
1367
  function getLongFlag(flags) {
@@ -1530,7 +1531,7 @@ function updateAgent(projectPath, language, packageManager, loadedAddons) {
1530
1531
  const agentFiles = [
1531
1532
  "AGENTS.md",
1532
1533
  "GEMINI.md",
1533
- "CLAUDE.md"
1534
+ ".claude/CLAUDE.md"
1534
1535
  ];
1535
1536
  const languageLabel = language === "typescript" ? "TypeScript" : language === "checkjs" ? "JavaScript (JSDoc)" : "None";
1536
1537
  const packageManagerLabel = packageManager ?? "npm";
@@ -1549,6 +1550,7 @@ function updateAgent(projectPath, language, packageManager, loadedAddons) {
1549
1550
  const agentPath = path.join(projectPath, fileName);
1550
1551
  if (!fs.existsSync(agentPath)) continue;
1551
1552
  let content = fs.readFileSync(agentPath, "utf-8");
1553
+ if (RX_IMPORT_ONLY.test(content)) continue;
1552
1554
  content = content.replace(existingSectionPattern, "");
1553
1555
  content = configSection + content;
1554
1556
  fs.writeFileSync(agentPath, content);
@@ -5734,6 +5736,263 @@ function createOptionBuilder(options) {
5734
5736
  };
5735
5737
  }
5736
5738
  //#endregion
5739
+ //#region src/addons/ai-tools.ts
5740
+ const REGEX_MD = /\.md$/;
5741
+ const CLIENTS = {
5742
+ "claude-code": {
5743
+ label: "Claude Code",
5744
+ pluginSettings: {
5745
+ path: ".claude/settings.json",
5746
+ marketplace: "svelte",
5747
+ repo: "sveltejs/ai-tools",
5748
+ id: "svelte@svelte"
5749
+ },
5750
+ agentPath: "AGENTS.md",
5751
+ agentLink: {
5752
+ path: ".claude/CLAUDE.md",
5753
+ contents: "@../AGENTS.md\n"
5754
+ },
5755
+ configPath: ".mcp.json",
5756
+ skillsPath: ".claude/skills",
5757
+ agentsPath: ".claude/agents",
5758
+ mcpOptions: {
5759
+ typeLocal: "stdio",
5760
+ typeRemote: "http",
5761
+ env: true
5762
+ }
5763
+ },
5764
+ cursor: {
5765
+ label: "Cursor",
5766
+ agentPath: "AGENTS.md",
5767
+ configPath: ".cursor/mcp.json",
5768
+ skillsPath: ".cursor/skills",
5769
+ agentsPath: ".cursor/agents",
5770
+ mcpOptions: {}
5771
+ },
5772
+ gemini: {
5773
+ label: "Gemini",
5774
+ agentPath: "GEMINI.md",
5775
+ configPath: ".gemini/settings.json",
5776
+ skillsPath: ".gemini/skills",
5777
+ agentsPath: ".gemini/agents",
5778
+ schema: "https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json",
5779
+ mcpOptions: {}
5780
+ },
5781
+ opencode: {
5782
+ label: "OpenCode",
5783
+ pluginOnly: true,
5784
+ agentPath: "AGENTS.md",
5785
+ configPath: ".opencode/opencode.json",
5786
+ schema: "https://opencode.ai/config.json",
5787
+ customData: { plugin: ["@sveltejs/opencode"] },
5788
+ extraFiles: [{
5789
+ path: ".opencode/svelte.json",
5790
+ data: { $schema: "https://svelte.dev/opencode/schema.json" }
5791
+ }]
5792
+ },
5793
+ vscode: {
5794
+ label: "VS Code",
5795
+ agentPath: "AGENTS.md",
5796
+ configPath: ".vscode/mcp.json",
5797
+ skillsPath: ".github/skills",
5798
+ agentsPath: ".github/agents",
5799
+ agentExtension: ".agent.md",
5800
+ mcpOptions: { serversKey: "servers" }
5801
+ },
5802
+ other: {
5803
+ label: "Other",
5804
+ agentPath: "AGENTS.md"
5805
+ }
5806
+ };
5807
+ const acceptsTools = (client) => Boolean(client && !client.pluginOnly && (client.skillsPath || client.agentsPath || client.configPath));
5808
+ const wantsLooseTools = (client, delivery) => acceptsTools(client) && (!client.pluginSettings || delivery !== "plugin");
5809
+ const wantsMcpConfig = (client, delivery) => Boolean(client?.mcpOptions) && wantsLooseTools(client, delivery);
5810
+ const TOOLS = {
5811
+ mcp: {
5812
+ label: "MCP server",
5813
+ kind: "mcp"
5814
+ },
5815
+ "svelte-code-writer": {
5816
+ label: "svelte-code-writer",
5817
+ kind: "skill",
5818
+ hint: "skill"
5819
+ },
5820
+ "svelte-core-bestpractices": {
5821
+ label: "svelte-core-bestpractices",
5822
+ kind: "skill",
5823
+ hint: "skill"
5824
+ },
5825
+ "svelte-file-editor": {
5826
+ label: "svelte-file-editor",
5827
+ kind: "agent",
5828
+ hint: "sub-agent"
5829
+ }
5830
+ };
5831
+ var ai_tools_default = defineAddon({
5832
+ id: "ai-tools",
5833
+ shortDescription: "Svelte AI tools",
5834
+ homepage: "https://svelte.dev/docs/ai",
5835
+ options: defineAddonOptions().add("ide", {
5836
+ question: "Which client would you like to use?",
5837
+ type: "multiselect",
5838
+ default: [],
5839
+ options: Object.entries(CLIENTS).map(([value, client]) => ({
5840
+ value,
5841
+ label: client.label
5842
+ })),
5843
+ required: true
5844
+ }).add("delivery", {
5845
+ question: "How would you like to add the Svelte tools?",
5846
+ type: "select",
5847
+ default: "plugin",
5848
+ options: [{
5849
+ value: "plugin",
5850
+ label: "Svelte plugin",
5851
+ hint: "recommended, auto-installs & updates"
5852
+ }, {
5853
+ value: "tools",
5854
+ label: "Individual tools",
5855
+ hint: "choose exactly what to add"
5856
+ }],
5857
+ condition: ({ ide }) => ide.some((i) => CLIENTS[i]?.pluginSettings)
5858
+ }).add("tools", {
5859
+ question: "Which tools would you like to add?",
5860
+ type: "multiselect",
5861
+ default: Object.keys(TOOLS),
5862
+ options: Object.entries(TOOLS).map(([value, t]) => ({
5863
+ value,
5864
+ label: t.label,
5865
+ hint: t.hint
5866
+ })),
5867
+ required: false,
5868
+ condition: ({ ide, delivery }) => ide.some((i) => wantsLooseTools(CLIENTS[i], delivery))
5869
+ }).add("mcpSetup", {
5870
+ question: "Which MCP setup would you like to use?",
5871
+ type: "select",
5872
+ default: "remote",
5873
+ options: [{
5874
+ value: "local",
5875
+ label: "Local",
5876
+ hint: "will use stdio"
5877
+ }, {
5878
+ value: "remote",
5879
+ label: "Remote",
5880
+ hint: "will use a remote endpoint"
5881
+ }],
5882
+ condition: ({ ide, delivery, tools }) => (tools === void 0 || tools.includes("mcp")) && ide.some((i) => wantsMcpConfig(CLIENTS[i], delivery))
5883
+ }).build(),
5884
+ run: ({ sv, options }) => {
5885
+ const usePlugin = options.delivery === "plugin";
5886
+ const selected = options.tools ?? Object.keys(TOOLS);
5887
+ const selectedSkills = selected.filter((t) => TOOLS[t]?.kind === "skill");
5888
+ const selectedAgents = selected.filter((t) => TOOLS[t]?.kind === "agent");
5889
+ const wantsMcp = selected.includes("mcp");
5890
+ const getLocalConfig = (o) => {
5891
+ return {
5892
+ ...o?.typeLocal ? { type: o.typeLocal } : {},
5893
+ command: o?.command ?? "npx",
5894
+ ...o?.env ? { env: {} } : {},
5895
+ ...o?.args === null ? {} : { args: o?.args ?? ["-y", "@sveltejs/mcp"] }
5896
+ };
5897
+ };
5898
+ const getRemoteConfig = (o) => {
5899
+ return {
5900
+ ...o?.typeRemote ? { type: o.typeRemote } : {},
5901
+ url: "https://mcp.svelte.dev/mcp"
5902
+ };
5903
+ };
5904
+ const filesAdded = [];
5905
+ const filesExistingAlready = [];
5906
+ const sharedFiles = getSharedFiles();
5907
+ const mcpFiles = sharedFiles.filter((file) => file.include.includes("mcp"));
5908
+ const skillFiles = sharedFiles.filter((file) => file.include.includes("skills"));
5909
+ const agentFiles = sharedFiles.filter((file) => file.include.includes("agents"));
5910
+ const agentFile = mcpFiles.find((file) => file.name === "AGENTS.md");
5911
+ const addFile = (path, contents) => {
5912
+ sv.file(path, (content) => {
5913
+ if (content) {
5914
+ filesExistingAlready.push(path);
5915
+ return false;
5916
+ }
5917
+ if (!filesAdded.includes(path)) filesAdded.push(path);
5918
+ return contents;
5919
+ });
5920
+ };
5921
+ for (const ide of options.ide) {
5922
+ const client = CLIENTS[ide];
5923
+ if (!client) continue;
5924
+ if (client.pluginSettings && usePlugin) {
5925
+ const plugin = client.pluginSettings;
5926
+ sv.file(plugin.path, transforms.json(({ data }) => {
5927
+ data.extraKnownMarketplaces ??= {};
5928
+ data.extraKnownMarketplaces[plugin.marketplace] ??= { source: {
5929
+ source: "github",
5930
+ repo: plugin.repo
5931
+ } };
5932
+ data.enabledPlugins ??= {};
5933
+ data.enabledPlugins[plugin.id] = true;
5934
+ }));
5935
+ continue;
5936
+ }
5937
+ if (!client.agentPath) continue;
5938
+ const { mcpOptions, agentPath, agentLink, configPath, skillsPath, agentsPath, agentExtension, schema, customData, extraFiles } = client;
5939
+ if (!filesAdded.includes(agentPath)) sv.file(agentPath, (content) => {
5940
+ if (content) {
5941
+ filesExistingAlready.push(agentPath);
5942
+ return false;
5943
+ }
5944
+ filesAdded.push(agentPath);
5945
+ return agentFile?.contents ?? "";
5946
+ });
5947
+ if (agentLink) addFile(agentLink.path, agentLink.contents);
5948
+ const writeMcp = Boolean(mcpOptions) && wantsMcp;
5949
+ if (configPath && (writeMcp || customData)) sv.file(configPath, transforms.json(({ data }) => {
5950
+ if (schema) data["$schema"] = schema;
5951
+ if (customData) for (const [key, value] of Object.entries(customData)) data[key] = value;
5952
+ if (writeMcp) {
5953
+ const key = mcpOptions.serversKey ?? "mcpServers";
5954
+ data[key] ??= {};
5955
+ data[key].svelte = options.mcpSetup === "local" ? getLocalConfig(mcpOptions) : getRemoteConfig(mcpOptions);
5956
+ }
5957
+ }));
5958
+ if (extraFiles) for (const extra of extraFiles) sv.file(extra.path, transforms.json(({ data }) => {
5959
+ for (const [key, value] of Object.entries(extra.data)) data[key] = value;
5960
+ }));
5961
+ if (skillsPath) for (const file of skillFiles) {
5962
+ if (!selectedSkills.includes(file.name.split("/")[0])) continue;
5963
+ addFile(`${skillsPath}/${file.name}`, file.contents);
5964
+ }
5965
+ if (agentsPath) for (const file of agentFiles) {
5966
+ if (!selectedAgents.includes(file.name.replace(REGEX_MD, ""))) continue;
5967
+ const ext = agentExtension ?? ".md";
5968
+ addFile(`${agentsPath}/${file.name.replace(REGEX_MD, ext)}`, file.contents);
5969
+ }
5970
+ }
5971
+ if (filesExistingAlready.length > 0) R$1.warn(`${filesExistingAlready.map((path) => color.path(path)).join(", ")} already exists, we didn't touch ${filesExistingAlready.length > 1 ? "them" : "it"}. See ${color.website("https://svelte.dev/docs/ai")} for manual setup.`);
5972
+ },
5973
+ nextSteps({ options, cwd }) {
5974
+ const steps = [];
5975
+ for (const ide of options.ide) {
5976
+ const client = CLIENTS[ide];
5977
+ if (!client?.pluginSettings) continue;
5978
+ const plugin = client.pluginSettings;
5979
+ const settingsPath = path.resolve(cwd, plugin.path);
5980
+ if (!fs.existsSync(settingsPath)) continue;
5981
+ let pluginEnabled = false;
5982
+ try {
5983
+ pluginEnabled = Boolean(JSON.parse(fs.readFileSync(settingsPath, "utf8")).enabledPlugins?.[plugin.id]);
5984
+ } catch {}
5985
+ if (!pluginEnabled) continue;
5986
+ const looseDirs = [client.skillsPath, client.agentsPath].filter((dir) => Boolean(dir) && fs.existsSync(path.resolve(cwd, dir)) && fs.readdirSync(path.resolve(cwd, dir)).some((entry) => Object.keys(TOOLS).includes(entry.replace(REGEX_MD, ""))));
5987
+ if (looseDirs.length === 0) continue;
5988
+ steps.push(`${client.label}: the Svelte plugin is enabled and ${looseDirs.map((d) => color.path(d)).join(" + ")} also exist${looseDirs.length > 1 ? "" : "s"} - remove ${looseDirs.length > 1 ? "them" : "it"} (or disable the plugin) to avoid duplicate skills/sub-agents.`);
5989
+ }
5990
+ if (options.delivery === "plugin" && options.ide.includes("claude-code")) steps.push(`Open the project in Claude Code and trust the workspace - the Svelte plugin installs automatically.`);
5991
+ if (options.ide.includes("other")) steps.push(`For other clients: ${color.website(`https://svelte.dev/docs/ai/${options.mcpSetup ?? "remote"}-setup#Other-clients`)}`);
5992
+ return steps;
5993
+ }
5994
+ });
5995
+ //#endregion
5737
5996
  //#region src/addons/common.ts
5738
5997
  const ESLINT_VERSION = "^10.4.1";
5739
5998
  const addEslintConfigPrettier = transforms.script(({ ast, js }) => {
@@ -5886,6 +6145,7 @@ var better_auth_default = defineAddon({
5886
6145
  runsAfter("experimental");
5887
6146
  },
5888
6147
  run: ({ sv, cwd, language, options, directory, dependencyVersion, file }) => {
6148
+ const lib = resolveLibPrefix(dependencyVersion("@sveltejs/kit"));
5889
6149
  const svelteVersion = dependencyVersion("svelte");
5890
6150
  const svelte5 = !!svelteVersion && coerceVersion(svelteVersion).major === 5;
5891
6151
  const [ts, s5] = createPrinter(language === "ts", svelte5);
@@ -5932,7 +6192,7 @@ var better_auth_default = defineAddon({
5932
6192
  }
5933
6193
  sv.file(`${directory.lib}/server/auth.${language}`, transforms.script(({ ast, comments, js }) => {
5934
6194
  js.imports.addNamed(ast, {
5935
- from: "$lib/server/db",
6195
+ from: `${lib}/server/db`,
5936
6196
  imports: [d1 ? "getDb" : "db"]
5937
6197
  });
5938
6198
  js.imports.addNamed(ast, {
@@ -5989,7 +6249,7 @@ var better_auth_default = defineAddon({
5989
6249
  /**
5990
6250
  * DO NOT USE!
5991
6251
  *
5992
- * This instance is used by the \`better-auth\` CLI for schema generation ONLY.
6252
+ * This instance is used by the \`auth\` CLI for schema generation ONLY.
5993
6253
  * To access \`auth\` at runtime, use \`event.locals.auth\`.
5994
6254
  */
5995
6255
  export const auth = createAuth(null${ts("!")});`;
@@ -6013,7 +6273,7 @@ var better_auth_default = defineAddon({
6013
6273
  const authConfigPath = `${directory.lib}/server/auth.${language}`;
6014
6274
  const authSchemaPath = `${directory.lib}/server/db/auth.schema.${language}`;
6015
6275
  sv.file(file.package, transforms.json(({ data, json }) => {
6016
- json.packageScriptsUpsert(data, "auth:schema", `better-auth generate --config ${authConfigPath} --output ${authSchemaPath} --yes`);
6276
+ json.packageScriptsUpsert(data, "auth:schema", `auth generate --config ${authConfigPath} --output ${authSchemaPath} --yes`);
6017
6277
  }));
6018
6278
  sv.file(`${directory.lib}/server/db/auth.schema.${language}`, transforms.text(({ content }) => {
6019
6279
  if (content) return false;
@@ -6028,7 +6288,7 @@ var better_auth_default = defineAddon({
6028
6288
  sv.file("src/app.d.ts", transforms.script(({ ast, comments, js }) => {
6029
6289
  if (d1) js.imports.addNamed(ast, {
6030
6290
  imports: ["createAuth"],
6031
- from: "$lib/server/auth"
6291
+ from: `${lib}/server/auth`
6032
6292
  });
6033
6293
  js.imports.addNamed(ast, {
6034
6294
  imports: ["User", "Session"],
@@ -6052,7 +6312,7 @@ var better_auth_default = defineAddon({
6052
6312
  });
6053
6313
  js.imports.addNamed(ast, {
6054
6314
  imports: [d1 ? "createAuth" : "auth"],
6055
- from: "$lib/server/auth"
6315
+ from: `${lib}/server/auth`
6056
6316
  });
6057
6317
  env.importEnv(ast, js, ["building"]);
6058
6318
  const handleContent = dedent`
@@ -6160,7 +6420,7 @@ var better_auth_default = defineAddon({
6160
6420
  import { fail, redirect } from '@sveltejs/kit';
6161
6421
  ${ts("import type { Actions } from './$types';")}
6162
6422
  ${ts("import type { PageServerLoad } from './$types';")}
6163
- ${!d1 ? "import { auth } from '$lib/server/auth';" : ""}
6423
+ ${!d1 ? `import { auth } from '${lib}/server/auth';` : ""}
6164
6424
  ${needsAPIError ? "import { APIError } from 'better-auth/api';" : ""}
6165
6425
 
6166
6426
  export const load${ts(": PageServerLoad")} = (event) => {
@@ -6229,7 +6489,7 @@ var better_auth_default = defineAddon({
6229
6489
  import { redirect } from '@sveltejs/kit';
6230
6490
  ${ts("import type { Actions } from './$types';")}
6231
6491
  ${ts("import type { PageServerLoad } from './$types';")}
6232
- ${!d1 ? "import { auth } from '$lib/server/auth';" : ""}
6492
+ ${!d1 ? `import { auth } from '${lib}/server/auth';` : ""}
6233
6493
 
6234
6494
  export const load${ts(": PageServerLoad")} = (event) => {
6235
6495
  if (!event.locals.user) {
@@ -6396,7 +6656,7 @@ var drizzle_default = defineAddon({
6396
6656
  runsAfter("experimental");
6397
6657
  if (!isKit) return unsupported("Requires SvelteKit");
6398
6658
  },
6399
- run: ({ sv, language, options, directory, dependencyVersion, cwd, cancel, file, packageManager }) => {
6659
+ run: ({ sv, language, options, directory, dependencyVersion, cwd, cancel, file }) => {
6400
6660
  const [ts] = createPrinter(language === "ts");
6401
6661
  const baseDBPath = path.resolve(cwd, directory.lib, "server", "db");
6402
6662
  const paths = {
@@ -6413,9 +6673,8 @@ var drizzle_default = defineAddon({
6413
6673
  if (options.postgresql === "neon") sv.devDependency("@neondatabase/serverless", "^1.1.0");
6414
6674
  if (options.postgresql === "postgres.js") sv.devDependency("postgres", "^3.4.9");
6415
6675
  if (options.sqlite === "better-sqlite3") {
6416
- sv.dependency("better-sqlite3", "^12.10.0");
6676
+ sv.dependency("better-sqlite3", "^13.0.2");
6417
6677
  sv.devDependency("@types/better-sqlite3", "^7.6.13");
6418
- if (packageManager === "pnpm") sv.file(file.findUp("pnpm-workspace.yaml"), pnpm.allowBuilds("better-sqlite3"));
6419
6678
  }
6420
6679
  if (options.sqlite === "libsql" || options.sqlite === "turso") sv.devDependency("@libsql/client", "^0.17.3");
6421
6680
  sv.file(".env", generateEnv(options, false));
@@ -6513,7 +6772,13 @@ var drizzle_default = defineAddon({
6513
6772
  })
6514
6773
  `) });
6515
6774
  }));
6516
- svelteConfig.edit({
6775
+ if (isKit3(dependencyVersion("@sveltejs/kit"))) {
6776
+ const configFile = language === "ts" ? "tsconfig.json" : "jsconfig.json";
6777
+ if (fileExists(cwd, configFile)) sv.file(configFile, transforms.json(({ data }) => {
6778
+ const include = data.include ??= ["src"];
6779
+ if (!include.includes(`drizzle.config.${language}`)) include.push(`drizzle.config.${language}`);
6780
+ }));
6781
+ } else svelteConfig.edit({
6517
6782
  sv,
6518
6783
  cwd
6519
6784
  }, ({ override, js }) => {
@@ -6929,12 +7194,25 @@ const FEATURES = {
6929
7194
  hint: "kit ^2 only",
6930
7195
  inNext: false
6931
7196
  },
6932
- handleRenderingErrors: { label: "rendering error boundaries" },
7197
+ handleRenderingErrors: {
7198
+ label: "rendering error boundaries",
7199
+ hint: "kit ^2 only",
7200
+ inNext: false
7201
+ },
6933
7202
  forkPreloads: {
6934
7203
  label: "forked preloading",
6935
7204
  off: true
6936
7205
  }
6937
7206
  };
7207
+ const SOURCE_EXTENSIONS = [
7208
+ ".svelte",
7209
+ ".svelte.ts",
7210
+ ".svelte.js",
7211
+ ".ts",
7212
+ ".js",
7213
+ ".svx",
7214
+ ".md"
7215
+ ];
6938
7216
  const KIT3_PEERS = {
6939
7217
  vite: "^8.0.0",
6940
7218
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
@@ -6966,7 +7244,7 @@ var experimental_default = defineAddon({
6966
7244
  required: false
6967
7245
  }).build(),
6968
7246
  setup: ({ runsAfter }) => runsAfter("sveltekitAdapter"),
6969
- run: ({ sv, cwd, options, language, dependencyVersion }) => {
7247
+ run: ({ sv, cwd, options, language, directory, dependencyVersion }) => {
6970
7248
  const kitNext = options.versions.includes("kit");
6971
7249
  if (kitNext) {
6972
7250
  sv.devDependency("@sveltejs/kit", "next");
@@ -6982,6 +7260,23 @@ var experimental_default = defineAddon({
6982
7260
  };
6983
7261
  for (const name of Object.keys(deps)) if (name.startsWith("@sveltejs/adapter-")) sv.devDependency(name, "next");
6984
7262
  }
7263
+ if (kitNext) {
7264
+ for (const name of ["tsconfig.json", "jsconfig.json"]) {
7265
+ if (!fileExists(cwd, name)) continue;
7266
+ sv.file(name, transforms.json(({ data }) => {
7267
+ data.extends = KIT3_TSCONFIG;
7268
+ data.include ??= [directory.src];
7269
+ for (const [key, value] of Object.entries(data.compilerOptions ?? {})) if (KIT3_TSCONFIG_DEFAULT[key] === value) delete data.compilerOptions[key];
7270
+ }));
7271
+ }
7272
+ sv.file("package.json", transforms.json(({ data }) => {
7273
+ data.imports = {
7274
+ ...libSubpathImports(directory.lib),
7275
+ ...data.imports
7276
+ };
7277
+ }));
7278
+ for (const relative of sourceFiles(cwd, directory.src)) sv.file(relative, (content) => content.includes("$lib") ? content.replaceAll("$lib", "#lib") : false);
7279
+ }
6985
7280
  const config = {};
6986
7281
  for (const [name, f] of Object.entries(FEATURES)) {
6987
7282
  if (!options.features.includes(name)) continue;
@@ -6997,147 +7292,12 @@ var experimental_default = defineAddon({
6997
7292
  }, ({ override }) => override(config));
6998
7293
  }
6999
7294
  });
7000
- var mcp_default = defineAddon({
7001
- id: "mcp",
7002
- shortDescription: "Svelte MCP",
7003
- homepage: "https://svelte.dev/docs/mcp",
7004
- options: defineAddonOptions().add("ide", {
7005
- question: "Which client would you like to use?",
7006
- type: "multiselect",
7007
- default: [],
7008
- options: [
7009
- {
7010
- value: "claude-code",
7011
- label: "claude code"
7012
- },
7013
- {
7014
- value: "cursor",
7015
- label: "Cursor"
7016
- },
7017
- {
7018
- value: "gemini",
7019
- label: "Gemini"
7020
- },
7021
- {
7022
- value: "opencode",
7023
- label: "opencode"
7024
- },
7025
- {
7026
- value: "vscode",
7027
- label: "VSCode"
7028
- },
7029
- {
7030
- value: "other",
7031
- label: "Other"
7032
- }
7033
- ],
7034
- required: true
7035
- }).add("setup", {
7036
- question: "What setup would you like to use?",
7037
- type: "select",
7038
- default: "remote",
7039
- options: [{
7040
- value: "local",
7041
- label: "Local",
7042
- hint: "will use stdio"
7043
- }, {
7044
- value: "remote",
7045
- label: "Remote",
7046
- hint: "will use a remote endpoint"
7047
- }],
7048
- required: true,
7049
- condition: ({ ide }) => !(ide.length === 1 && ide.includes("opencode"))
7050
- }).build(),
7051
- run: ({ sv, options }) => {
7052
- const getLocalConfig = (o) => {
7053
- return {
7054
- ...o?.typeLocal ? { type: o.typeLocal } : {},
7055
- command: o?.command ?? "npx",
7056
- ...o?.env ? { env: {} } : {},
7057
- ...o?.args === null ? {} : { args: o?.args ?? ["-y", "@sveltejs/mcp"] }
7058
- };
7059
- };
7060
- const getRemoteConfig = (o) => {
7061
- return {
7062
- ...o?.typeRemote ? { type: o.typeRemote } : {},
7063
- url: "https://mcp.svelte.dev/mcp"
7064
- };
7065
- };
7066
- const configurator = {
7067
- "claude-code": {
7068
- agentPath: "CLAUDE.md",
7069
- configPath: ".mcp.json",
7070
- mcpOptions: {
7071
- typeLocal: "stdio",
7072
- typeRemote: "http",
7073
- env: true
7074
- }
7075
- },
7076
- cursor: {
7077
- agentPath: "AGENTS.md",
7078
- configPath: ".cursor/mcp.json",
7079
- mcpOptions: {}
7080
- },
7081
- gemini: {
7082
- agentPath: "GEMINI.md",
7083
- configPath: ".gemini/settings.json",
7084
- schema: "https://raw.githubusercontent.com/google-gemini/gemini-cli/main/schemas/settings.schema.json",
7085
- mcpOptions: {}
7086
- },
7087
- opencode: {
7088
- agentPath: "AGENTS.md",
7089
- configPath: ".opencode/opencode.json",
7090
- schema: "https://opencode.ai/config.json",
7091
- customData: { plugin: ["@sveltejs/opencode"] },
7092
- extraFiles: [{
7093
- path: ".opencode/svelte.json",
7094
- data: { $schema: "https://svelte.dev/opencode/schema.json" }
7095
- }]
7096
- },
7097
- vscode: {
7098
- agentPath: "AGENTS.md",
7099
- configPath: ".vscode/mcp.json",
7100
- mcpOptions: { serversKey: "servers" }
7101
- },
7102
- other: { other: true }
7103
- };
7104
- const filesAdded = [];
7105
- const filesExistingAlready = [];
7106
- const agentFile = getSharedFiles().filter((file) => file.include.includes("mcp")).find((file) => file.name === "AGENTS.md");
7107
- for (const ide of options.ide) {
7108
- const value = configurator[ide];
7109
- if (value === void 0) continue;
7110
- if ("other" in value) continue;
7111
- const { mcpOptions, agentPath, configPath, schema, customData, extraFiles } = value;
7112
- if (!filesAdded.includes(agentPath)) sv.file(agentPath, (content) => {
7113
- if (content) {
7114
- filesExistingAlready.push(agentPath);
7115
- return false;
7116
- }
7117
- filesAdded.push(agentPath);
7118
- return agentFile?.contents ?? "";
7119
- });
7120
- sv.file(configPath, transforms.json(({ data }) => {
7121
- if (schema) data["$schema"] = schema;
7122
- if (customData) for (const [key, value] of Object.entries(customData)) data[key] = value;
7123
- if (mcpOptions) {
7124
- const key = mcpOptions.serversKey ?? "mcpServers";
7125
- data[key] ??= {};
7126
- data[key].svelte = options.setup === "local" ? getLocalConfig(mcpOptions) : getRemoteConfig(mcpOptions);
7127
- }
7128
- }));
7129
- if (extraFiles) for (const extra of extraFiles) sv.file(extra.path, transforms.json(({ data }) => {
7130
- for (const [key, value] of Object.entries(extra.data)) data[key] = value;
7131
- }));
7132
- }
7133
- if (filesExistingAlready.length > 0) R$1.warn(`${filesExistingAlready.map((path) => color.path(path)).join(", ")} already exists, we didn't touch ${filesExistingAlready.length > 1 ? "them" : "it"}. See ${color.website("https://svelte.dev/docs/mcp/overview#Usage")} for manual setup.`);
7134
- },
7135
- nextSteps({ options }) {
7136
- const steps = [];
7137
- if (options.ide.includes("other")) steps.push(`For other clients: ${color.website(`https://svelte.dev/docs/mcp/${options.setup}-setup#Other-clients`)}`);
7138
- return steps;
7139
- }
7140
- });
7295
+ /** Workspace-relative source files under `src`, for the `$lib` -> `#lib` rewrite. */
7296
+ function sourceFiles(cwd, src) {
7297
+ const root = path.resolve(cwd, src);
7298
+ if (!fs.existsSync(root)) return [];
7299
+ return fs.readdirSync(root, { recursive: true }).map((entry) => path.join(src, entry)).filter((relative) => SOURCE_EXTENSIONS.some((ext) => relative.endsWith(ext)) && fs.statSync(path.resolve(cwd, relative)).isFile());
7300
+ }
7141
7301
  //#endregion
7142
7302
  //#region src/addons/mdsvex.ts
7143
7303
  var mdsvex_default = defineAddon({
@@ -7206,11 +7366,15 @@ var paraglide_default = defineAddon({
7206
7366
  shortDescription: "i18n",
7207
7367
  homepage: "https://inlang.com/m/gerre34r/library-inlang-paraglideJs",
7208
7368
  options: options$3,
7209
- setup: ({ isKit, unsupported }) => {
7369
+ setup: ({ isKit, unsupported, runsAfter }) => {
7210
7370
  if (!isKit) unsupported("Requires SvelteKit");
7371
+ runsAfter("experimental");
7211
7372
  },
7212
- run: ({ sv, options, file, language, directory }) => {
7373
+ run: ({ sv, options, file, language, directory, dependencyVersion }) => {
7213
7374
  const [ts] = createPrinter(language === "ts");
7375
+ const kitRange = dependencyVersion("@sveltejs/kit");
7376
+ const lib = resolveLibPrefix(kitRange);
7377
+ const pathType = isKit3(kitRange) ? "Path" : "Pathname";
7214
7378
  const paraglideOutDir = `${directory.lib}/paraglide`;
7215
7379
  sv.devDependency("@inlang/paraglide-js", "^2.18.2");
7216
7380
  sv.file(file.viteConfig, transforms.script(({ ast, js }) => {
@@ -7227,7 +7391,7 @@ var paraglide_default = defineAddon({
7227
7391
  }));
7228
7392
  sv.file(`src/hooks.${language}`, transforms.script(({ ast, comments, js }) => {
7229
7393
  js.imports.addNamed(ast, {
7230
- from: "$lib/paraglide/runtime",
7394
+ from: `${lib}/paraglide/runtime`,
7231
7395
  imports: ["deLocalizeUrl"]
7232
7396
  });
7233
7397
  const expression = js.common.parseExpression("(request) => deLocalizeUrl(request.url).pathname");
@@ -7250,11 +7414,11 @@ var paraglide_default = defineAddon({
7250
7414
  }));
7251
7415
  sv.file(`src/hooks.server.${language}`, transforms.script(({ ast, comments, js }) => {
7252
7416
  js.imports.addNamed(ast, {
7253
- from: "$lib/paraglide/server",
7417
+ from: `${lib}/paraglide/server`,
7254
7418
  imports: ["paraglideMiddleware"]
7255
7419
  });
7256
7420
  js.imports.addNamed(ast, {
7257
- from: "$lib/paraglide/runtime",
7421
+ from: `${lib}/paraglide/runtime`,
7258
7422
  imports: ["getTextDirection"]
7259
7423
  });
7260
7424
  js.kit.addHooksHandle(ast, {
@@ -7294,7 +7458,7 @@ var paraglide_default = defineAddon({
7294
7458
  sv.file(`${directory.kitRoutes}/+layout.svelte`, transforms.svelteScript({ language }, ({ ast, svelte, js }) => {
7295
7459
  js.imports.addNamed(ast.instance.content, {
7296
7460
  imports: ["locales", "localizeHref"],
7297
- from: "$lib/paraglide/runtime"
7461
+ from: `${lib}/paraglide/runtime`
7298
7462
  });
7299
7463
  js.imports.addNamed(ast.instance.content, {
7300
7464
  imports: ["page"],
@@ -7305,14 +7469,14 @@ var paraglide_default = defineAddon({
7305
7469
  from: "$app/paths"
7306
7470
  });
7307
7471
  if (language === "ts") js.imports.addNamed(ast.instance.content, {
7308
- imports: ["Pathname"],
7472
+ imports: [pathType],
7309
7473
  from: "$app/types",
7310
7474
  isType: true
7311
7475
  });
7312
7476
  svelte.addFragment(ast, dedent`
7313
7477
  <div style="display:none">
7314
7478
  {#each locales as locale (locale)}
7315
- <a href={resolve(localizeHref(page.url.pathname, { locale })${ts(" as Pathname")})}>{locale}</a>
7479
+ <a href={resolve(localizeHref(page.url.pathname, { locale })${ts(` as ${pathType}`)})}>{locale}</a>
7316
7480
  {/each}
7317
7481
  </div>`, { language });
7318
7482
  }));
@@ -7321,11 +7485,11 @@ var paraglide_default = defineAddon({
7321
7485
  sv.file(`${directory.kitRoutes}/demo/paraglide/+page.svelte`, transforms.svelteScript({ language }, ({ ast, svelte, js }) => {
7322
7486
  js.imports.addNamed(ast.instance.content, {
7323
7487
  imports: { m: "m" },
7324
- from: "$lib/paraglide/messages.js"
7488
+ from: `${lib}/paraglide/messages.js`
7325
7489
  });
7326
7490
  js.imports.addNamed(ast.instance.content, {
7327
7491
  imports: { setLocale: "setLocale" },
7328
- from: "$lib/paraglide/runtime"
7492
+ from: `${lib}/paraglide/runtime`
7329
7493
  });
7330
7494
  let templateCode = "<h1>{m.hello_world({ name: 'SvelteKit User' })}</h1>";
7331
7495
  const { validLanguageTags } = parseLanguageTagInput(options.languageTags);
@@ -7603,7 +7767,7 @@ var sveltekit_adapter_default = defineAddon({
7603
7767
  });
7604
7768
  if (adapter.package === "@sveltejs/adapter-cloudflare") {
7605
7769
  sv.devDependency("wrangler", "^4.97.0");
7606
- if (packageManager === "pnpm") sv.file(file.findUp("pnpm-workspace.yaml"), pnpm.allowBuilds("workerd", "sharp"));
7770
+ if (packageManager === "pnpm") sv.file(file.findUp("pnpm-workspace.yaml"), pnpm.allowBuilds("workerd"));
7607
7771
  const ext = fileExists(cwd, "wrangler.toml") ? "toml" : "jsonc";
7608
7772
  const applyWranglerConfig = (data) => {
7609
7773
  if (ext === "jsonc") data.$schema ??= "./node_modules/wrangler/config-schema.json";
@@ -7679,11 +7843,10 @@ var tailwindcss_default = defineAddon({
7679
7843
  default: [],
7680
7844
  required: false
7681
7845
  }).build(),
7682
- run: ({ sv, options, file, isKit, directory, dependencyVersion, language, packageManager }) => {
7846
+ run: ({ sv, options, file, isKit, directory, dependencyVersion, language }) => {
7683
7847
  const prettierInstalled = Boolean(dependencyVersion("prettier"));
7684
7848
  sv.devDependency("tailwindcss", "^4.3.0");
7685
7849
  sv.devDependency("@tailwindcss/vite", "^4.3.0");
7686
- if (packageManager === "pnpm") sv.file(file.findUp("pnpm-workspace.yaml"), pnpm.allowBuilds("@tailwindcss/oxide"));
7687
7850
  if (prettierInstalled) sv.devDependency("prettier-plugin-tailwindcss", "^0.8.0");
7688
7851
  for (const plugin of plugins) {
7689
7852
  if (!options.plugins.includes(plugin.id)) continue;
@@ -7921,7 +8084,7 @@ const officialAddons$1 = {
7921
8084
  mdsvex: mdsvex_default,
7922
8085
  paraglide: paraglide_default,
7923
8086
  storybook: storybook_default,
7924
- mcp: mcp_default,
8087
+ aiTools: ai_tools_default,
7925
8088
  experimental: experimental_default
7926
8089
  };
7927
8090
  function getAddonDetails(id) {
@@ -10737,31 +10900,40 @@ async function formatFiles(options) {
10737
10900
  const { start, stop } = vt();
10738
10901
  start("Formatting modified files");
10739
10902
  const args = [
10740
- "prettier",
10741
10903
  "--write",
10742
10904
  "--ignore-unknown",
10743
10905
  ...options.filesToFormat
10744
10906
  ];
10745
- const cmd = resolveCommand(options.packageManager, "execute-local", args);
10907
+ let result = await run("prettier", args, options.cwd);
10908
+ if (result.notFound) {
10909
+ const cmd = resolveCommand(options.packageManager, "execute-local", ["prettier", ...args]);
10910
+ result = await run(cmd.command, cmd.args, options.cwd);
10911
+ }
10912
+ if (result.error !== void 0) {
10913
+ stop("Failed to format files");
10914
+ R$1.error(result.error);
10915
+ return;
10916
+ }
10917
+ stop("Successfully formatted modified files");
10918
+ }
10919
+ async function run(command, args, cwd) {
10746
10920
  try {
10747
- const result = await z(cmd.command, cmd.args, {
10921
+ await z(command, args, {
10748
10922
  nodeOptions: {
10749
- cwd: options.cwd,
10923
+ cwd,
10750
10924
  stdio: "pipe"
10751
10925
  },
10752
10926
  throwOnError: true
10753
10927
  });
10754
- if (result.exitCode !== 0) {
10755
- stop("Failed to format files");
10756
- R$1.error(result.stderr);
10757
- return;
10758
- }
10928
+ return {};
10759
10929
  } catch (e) {
10760
- stop("Failed to format files");
10761
- R$1.error(e?.output?.stderr || "unknown error");
10762
- return;
10930
+ if (e?.code === "ENOENT") return {
10931
+ notFound: true,
10932
+ error: `${command} not found`
10933
+ };
10934
+ const output = e?.output;
10935
+ return { error: [output?.stderr, output?.stdout].filter(Boolean).join("\n").trim() || (e instanceof Error ? e.message : "unknown error") };
10763
10936
  }
10764
- stop("Successfully formatted modified files");
10765
10937
  }
10766
10938
  //#endregion
10767
10939
  //#region src/core/package-manager.ts
@@ -12362,10 +12534,30 @@ async function runAddon({ addon, loaded, multiple, workspace, workspaceOptions }
12362
12534
  cancels
12363
12535
  };
12364
12536
  }
12537
+ /**
12538
+ * Orders add-ons so every `runsAfter` is honoured, keeping the original order between add-ons that
12539
+ * don't constrain each other. Cycles and unknown ids are ignored rather than fatal - an add-on that
12540
+ * can't be placed simply keeps its position.
12541
+ */
12365
12542
  function orderAddons(addons, setupResults) {
12366
- return addons.sort((a, b) => {
12367
- return setupResults[a.id]?.runsAfter?.length - setupResults[b.id]?.runsAfter?.length;
12368
- });
12543
+ const byId = new Map(addons.map((addon) => [addon.id, addon]));
12544
+ const ordered = [];
12545
+ const placed = /* @__PURE__ */ new Set();
12546
+ const visiting = /* @__PURE__ */ new Set();
12547
+ const place = (addon) => {
12548
+ if (placed.has(addon.id) || visiting.has(addon.id)) return;
12549
+ visiting.add(addon.id);
12550
+ for (const id of setupResults[addon.id]?.runsAfter ?? []) {
12551
+ const dependency = byId.get(id);
12552
+ if (dependency) place(dependency);
12553
+ }
12554
+ visiting.delete(addon.id);
12555
+ placed.add(addon.id);
12556
+ ordered.push(addon);
12557
+ };
12558
+ const seeded = [...addons].sort((a, b) => (setupResults[a.id]?.runsAfter?.length ?? 0) - (setupResults[b.id]?.runsAfter?.length ?? 0));
12559
+ for (const addon of seeded) place(addon);
12560
+ return ordered;
12369
12561
  }
12370
12562
  //#endregion
12371
12563
  export { __toESM as S, name as _, detectPackageManager as a, __commonJSMin as b, defineAddonOptions as c, Command as d, R as f, helpConfig as g, forwardExitCode as h, addPnpmAllowBuilds as i, from as l, create$1 as m, add$1 as n, officialAddons$1 as o, z as p, create as r, defineAddon as s, add as t, program as u, version as v, __require as x, svDeprecated as y };
package/dist/shared.json CHANGED
@@ -22,6 +22,14 @@
22
22
  "exclude": [],
23
23
  "contents": "# [sv](https://svelte.dev/docs/cli/overview) community add-on: [~SV-NAME-TODO~](https://github.com/~SV-NAME-TODO~)\n\n> [!IMPORTANT]\n> Svelte maintainers have not reviewed community add-ons for malicious code! Use at your discretion.\n\n## Usage\n\nTo install the add-on, run:\n\n```shell\nnpx sv add ~SV-PROTOCOL-NAME-TODO~\n```\n\n## What you get [TO BE FILLED...]\n\n- A super cool stuff\n- Another one!\n\n## Options [TO BE FILLED...]\n\n### `who`\n\nThe name of the person to say hello to.\n\nDefault: `you`\n\n```shell\nnpx sv add ~SV-PROTOCOL-NAME-TODO~=\"who:your-name\"\n```\n"
24
24
  },
25
+ {
26
+ "name": "svelte-file-editor.md",
27
+ "include": [
28
+ "agents"
29
+ ],
30
+ "exclude": [],
31
+ "contents": "---\nname: svelte-file-editor\ndescription: Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server or the `svelte-file-editor` skill if they are available. Fetches relevant documentation and validates code using the Svelte MCP server tools.\n---\n\nYou are a Svelte 5 expert responsible for writing, editing, and validating Svelte components and modules. You have access to the Svelte MCP server which provides documentation and code analysis tools. Always use the tools from the Svelte MCP server to fetch documentation with `get_documentation` and validate the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.\n\nIf the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.\n\nIf the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.\n\n## Available MCP tools\n\n### 1. list-sections\n\nLists all available Svelte 5 and SvelteKit documentation sections with titles and paths. Use this first to discover what documentation is available.\n\n### 2. get-documentation\n\nRetrieves full documentation for specified sections. Accepts a single section name or an array of section names. Use after `list-sections` to fetch relevant docs for the task at hand.\n\n**Example sections:** `$state`, `$derived`, `$effect`, `$props`, `$bindable`, `snippets`, `routing`, `load functions`\n\n### 3. svelte-autofixer\n\nAnalyzes Svelte code and returns suggestions to fix issues. Pass the component code directly to this tool. It will detect common mistakes like:\n\n- Using `$effect` instead of `$derived` for computations\n- Missing cleanup in effects\n- Svelte 4 syntax (`on:click`, `export let`, `<slot>`)\n- Missing keys in `{#each}` blocks\n- And more\n\n## Workflow\n\nWhen invoked to work on a Svelte file:\n\n### 1. Gather context (if needed)\n\nIf you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:\n\n1. Call `list-sections` to see available documentation\n2. Call `get-documentation` with relevant section names\n\n### 2. Read the target file\n\nRead the file to understand the current implementation.\n\n### 3. Make changes\n\nApply edits following Svelte 5 best practices:\n\n### 4. Validate changes\n\nAfter editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.\n\n### 5. Fix any issues\n\nIf the autofixer reports problems, fix them and re-validate until no issues remain.\n\n## Output format\n\nAfter completing your work, provide:\n\n1. Summary of changes made\n2. Any issues found and fixed by the autofixer\n3. Recommendations for further improvements (if any)\n"
32
+ },
25
33
  {
26
34
  "name": "jsconfig.json",
27
35
  "include": [
@@ -70,6 +78,94 @@
70
78
  "exclude": [],
71
79
  "contents": "<script lang=\"ts\">\n\timport favicon from '$lib/assets/favicon.svg';\n\n\tlet { children } = $props();\n\n\tconst title = '$sv-title-$sv';\n\tconst href = '$sv-url-$sv';\n\n\tlet prefersDark = $state(true);\n\tlet isDark = $state(true);\n\n\tfunction switchTheme() {\n\t\tconst value = isDark ? 'light' : 'dark';\n\n\t\tisDark = value === 'dark';\n\t\tlocalStorage.setItem('sv:theme', isDark === prefersDark ? 'system' : value);\n\t}\n\n\t$effect(() => {\n\t\tdocument.documentElement.classList.remove('light', 'dark');\n\t\tprefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n\n\t\tconst theme = localStorage.getItem('sv:theme');\n\n\t\tisDark = !theme ? prefersDark : theme === 'dark' || (theme === 'system' && prefersDark);\n\t\tdocument.documentElement.classList.add(isDark ? 'dark' : 'light');\n\t});\n</script>\n\n<svelte:head>\n\t<title>--from-playground {title}</title>\n\t<script>\n\t\t{\n\t\t\tconst theme = localStorage.getItem('sv:theme');\n\n\t\t\tdocument.documentElement.classList.add(\n\t\t\t\t!theme || theme === 'system'\n\t\t\t\t\t? window.matchMedia('(prefers-color-scheme: dark)').matches\n\t\t\t\t\t\t? 'dark'\n\t\t\t\t\t\t: 'light'\n\t\t\t\t\t: theme\n\t\t\t);\n\t\t}\n\t</script>\n</svelte:head>\n\n<div class=\"layout\">\n\t<nav class=\"navbar\">\n\t\t<div class=\"nav-left\">\n\t\t\t<a href=\"/\" class=\"svelte-icon\">\n\t\t\t\t<img src={favicon} alt=\"Svelte\" width=\"32\" height=\"32\" />\n\t\t\t</a>\n\t\t\t<p class=\"title\">{title}</p>\n\t\t</div>\n\t\t<div class=\"nav-right\">\n\t\t\t<a {href} class=\"raised\" target=\"_blank\" rel=\"noopener noreferrer\">\n\t\t\t\t--to-playground\n\t\t\t\t<span aria-hidden=\"true\" style=\"margin-left:0.25em;\"> ↗</span>\n\t\t\t</a>\n\t\t\t<button class=\"raised theme-toggle\" onclick={switchTheme} aria-label=\"Toggle theme\">\n\t\t\t\t<span class=\"icon\"></span>\n\t\t\t</button>\n\t\t</div>\n\t</nav>\n\n\t<main class=\"content\">\n\t\t{@render children()}\n\t</main>\n</div>\n\n<style>\n\t:global(body) {\n\t\tmargin: 0;\n\t}\n\n\t:global(html) {\n\t\tmargin: 0;\n\t\t--bg-1: hsl(0, 0%, 100%);\n\t\t--bg-2: hsl(206, 20%, 90%);\n\t\t--bg-3: hsl(206, 20%, 80%);\n\t\t--navbar-bg: #fff;\n\t\t--fg-1: hsl(0, 0%, 13%);\n\t\t--fg-2: hsl(0, 0%, 50%);\n\t\t--fg-3: hsl(0, 0%, 60%);\n\t\t--link: hsl(208, 77%, 47%);\n\t\t--border-radius: 4px;\n\t\t--font:\n\t\t\t-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',\n\t\t\t'Helvetica Neue', sans-serif;\n\t\tcolor-scheme: light;\n\t\tbackground: var(--bg-1);\n\t\tcolor: var(--fg-1);\n\t\tfont-family: var(--font);\n\t\tline-height: 1.5;\n\t\theight: calc(100vh - 2rem);\n\t\taccent-color: var(--link) !important;\n\t\tmin-height: 100vh;\n\t\tbackground-color: var(--bg-1);\n\t}\n\n\t:global(html.dark) {\n\t\tcolor-scheme: dark;\n\t\t--bg-1: hsl(0, 0%, 18%);\n\t\t--bg-2: hsl(0, 0%, 30%);\n\t\t--bg-3: hsl(0, 0%, 40%);\n\t\t--navbar-bg: hsl(220, 14%, 16%);\n\t\t--fg-1: hsl(0, 0%, 75%);\n\t\t--fg-2: hsl(0, 0%, 40%);\n\t\t--fg-3: hsl(0, 0%, 30%);\n\t\t--link: hsl(206, 96%, 72%);\n\t}\n\n\t.navbar {\n\t\tcolor: var(--fg-1);\n\t\tdisplay: flex;\n\t\tjustify-content: space-between;\n\t\talign-items: center;\n\t\tpadding: 0em 2.5rem;\n\t\theight: 3.7rem;\n\t\tbackground-color: var(--navbar-bg);\n\t\tbox-shadow:\n\t\t\t0 2px 8px 0 rgba(0, 0, 0, 0.08),\n\t\t\t0 1.5px 4px 0 rgba(0, 0, 0, 0.04);\n\t}\n\n\t.nav-left {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 0.5rem;\n\t}\n\n\t.svelte-icon {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\ttext-decoration: none;\n\t\ttransition: opacity 0.2s ease;\n\t}\n\n\t.svelte-icon:hover {\n\t\topacity: 0.8;\n\t}\n\n\t.title {\n\t\tfont-size: 1.5rem;\n\t\tfont-weight: 400;\n\t\tmargin: 0;\n\t}\n\n\t.nav-right {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 1rem;\n\t}\n\n\t.raised {\n\t\tbackground: var(--navbar-bg);\n\t\tborder-left: 0.5px solid var(--fg-3);\n\t\tborder-top: 0.5px solid var(--fg-3);\n\t\tborder-bottom: none;\n\t\tborder-right: none;\n\t\tborder-radius: var(--border-radius);\n\t\tcolor: var(--fg-1);\n\t\tcursor: pointer;\n\t\ttransition: all 0.2s ease;\n\t\tbox-shadow:\n\t\t\t0 2px 4px rgba(0, 0, 0, 0.1),\n\t\t\t0 1px 2px rgba(0, 0, 0, 0.06);\n\t\ttext-decoration: none;\n\t\tfont-weight: 500;\n\t\tpadding: 0.25rem 0.75rem;\n\t\tfont-size: 0.8rem;\n\t}\n\n\t.raised:hover {\n\t\tborder-left-color: var(--fg-2);\n\t\tborder-top-color: var(--fg-2);\n\t\tbox-shadow:\n\t\t\t0 4px 8px rgba(0, 0, 0, 0.15),\n\t\t\t0 2px 4px rgba(0, 0, 0, 0.1);\n\t\ttransform: translate(-1px, -1px);\n\t}\n\n\t.content {\n\t\tpadding: 1rem;\n\t\tcolor: var(--fg-1);\n\t}\n\n\t.theme-toggle {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\twidth: 1.8rem;\n\t\theight: 1.8rem;\n\t\tpadding: 0;\n\t\tmin-width: 2rem;\n\t}\n\n\t.icon {\n\t\tdisplay: inline-block;\n\t\twidth: 1.5rem;\n\t\theight: 1.5rem;\n\t\t-webkit-mask-size: 1.5rem;\n\t\tmask-size: 1.5rem;\n\t\t-webkit-mask-repeat: no-repeat;\n\t\tmask-repeat: no-repeat;\n\t\t-webkit-mask-position: center;\n\t\tmask-position: center;\n\t\tbackground-color: var(--fg-1);\n\t}\n\n\t.icon {\n\t\tmask-image: url('data:image/svg+xml,%3csvg%20xmlns=\"http://www.w3.org/2000/svg\"%20viewBox=\"0%200%2024%2024\"%3e%3cpath%20fill=\"%23666\"%20d=\"M12%2021q-3.775%200-6.388-2.613T3%2012q0-3.45%202.25-5.988T11%203.05q.625-.075.975.45t-.025%201.1q-.425.65-.638%201.375T11.1%207.5q0%202.25%201.575%203.825T16.5%2012.9q.775%200%201.538-.225t1.362-.625q.525-.35%201.075-.037t.475.987q-.35%203.45-2.937%205.725T12%2021Zm0-2q2.2%200%203.95-1.213t2.55-3.162q-.5.125-1%20.2t-1%20.075q-3.075%200-5.238-2.163T9.1%207.5q0-.5.075-1t.2-1q-1.95.8-3.163%202.55T5%2012q0%202.9%202.05%204.95T12%2019Zm-.25-6.75Z\"/%3e%3c/svg%3e');\n\t}\n\n\t:global(html.dark) .icon {\n\t\tmask-image: url(\"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3e%3cpath%20fill='%23d4d4d4'%20d='M12%2019a1%201%200%200%201%20.993.883L13%2020v1a1%201%200%200%201-1.993.117L11%2021v-1a1%201%200%200%201%201-1zm6.313-2.09.094.083.7.7a1%201%200%200%201-1.32%201.497l-.094-.083-.7-.7a1%201%200%200%201%201.218-1.567l.102.07zm-11.306.083a1%201%200%200%201%20.083%201.32l-.083.094-.7.7a1%201%200%200%201-1.497-1.32l.083-.094.7-.7a1%201%200%200%201%201.414%200zM4%2011a1%201%200%200%201%20.117%201.993L4%2013H3a1%201%200%200%201-.117-1.993L3%2011h1zm17%200a1%201%200%200%201%20.117%201.993L21%2013h-1a1%201%200%200%201-.117-1.993L20%2011h1zM6.213%204.81l.094.083.7.7a1%201%200%200%201-1.32%201.497l-.094-.083-.7-.7A1%201%200%200%201%206.11%204.74l.102.07zm12.894.083a1%201%200%200%201%20.083%201.32l-.083.094-.7.7a1%201%200%200%201-1.497-1.32l.083-.094.7-.7a1%201%200%200%201%201.414%200zM12%202a1%201%200%200%201%20.993.883L13%203v1a1%201%200%200%201-1.993.117L11%204V3a1%201%200%200%201%201-1zm0%205a5%205%200%201%201-4.995%205.217L7%2012l.005-.217A5%205%200%200%201%2012%207z'/%3e%3c/svg%3e\");\n\t}\n</style>\n"
72
80
  },
81
+ {
82
+ "name": "svelte-code-writer/SKILL.md",
83
+ "include": [
84
+ "skills"
85
+ ],
86
+ "exclude": [],
87
+ "contents": "---\nname: svelte-code-writer\ndescription: CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating, editing or analyzing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.\n---\n\n## CLI tools\n\nYou have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:\n\n### List documentation sections\n\n```bash\nnpx @sveltejs/mcp list-sections\n```\n\nLists all available Svelte 5 and SvelteKit documentation sections with titles and paths.\n\n### Get documentation\n\n```bash\nnpx @sveltejs/mcp get-documentation \"<section1>,<section2>,...\"\n```\n\nRetrieves full documentation for specified sections. Use after `list-sections` to fetch relevant docs.\n\n**Example:**\n\n```bash\nnpx @sveltejs/mcp get-documentation \"$state,$derived,$effect\"\n```\n\n### Svelte autofixer\n\n```bash\nnpx @sveltejs/mcp svelte-autofixer \"<code_or_path>\" [options]\n```\n\nAnalyzes Svelte code and suggests fixes for common issues.\n\n**Options:**\n\n- `--async` - Enable async Svelte mode (default: false)\n- `--svelte-version` - Target version: 4 or 5 (default: 5)\n\n**Examples:**\n\n```bash\n# Analyze inline code (escape $ as \\$)\nnpx @sveltejs/mcp svelte-autofixer '<script>let count = \\$state(0);</script>'\n\n# Analyze a file\nnpx @sveltejs/mcp svelte-autofixer ./src/lib/Component.svelte\n\n# Target Svelte 4\nnpx @sveltejs/mcp svelte-autofixer ./Component.svelte --svelte-version 4\n```\n\n**Important:** When passing code with runes (`$state`, `$derived`, etc.) via the terminal, escape the `$` character as `\\$` to prevent shell variable substitution.\n\n## Workflow\n\n1. **Uncertain about syntax?** Run `list-sections` then `get-documentation` for relevant topics\n2. **Reviewing/debugging?** Run `svelte-autofixer` on the code to detect issues\n3. **Always validate** - Run `svelte-autofixer` before finalizing any Svelte component\n"
88
+ },
89
+ {
90
+ "name": "svelte-core-bestpractices/SKILL.md",
91
+ "include": [
92
+ "skills"
93
+ ],
94
+ "exclude": [],
95
+ "contents": "---\nname: svelte-core-bestpractices\ndescription: Guidance on writing fast, robust, modern Svelte code. Load this skill whenever in a Svelte project and asked to write/edit or analyze a Svelte component or module. Covers reactivity, event handling, styling, integration with libraries and more.\n---\n\n## `$state`\n\nOnly use the `$state` rune for variables that should be _reactive_ — in other words, variables that cause an `$effect`, `$derived` or template expression to update. Everything else can be a normal variable.\n\nObjects and arrays (`$state({...})` or `$state([...])`) are made deeply reactive, meaning mutation will trigger updates. This has a trade-off: in exchange for fine-grained reactivity, the objects must be proxied, which has performance overhead. In cases where you're dealing with large objects that are only ever reassigned (rather than mutated), use `$state.raw` instead. This is often the case with API responses, for example.\n\n## `$derived`\n\nTo compute something from state, use `$derived` rather than `$effect`:\n\n```js\n// do this\nlet square = $derived(num * num);\n\n// don't do this\nlet square;\n\n$effect(() => {\n\tsquare = num * num;\n});\n```\n\n> [!NOTE] `$derived` is given an expression, _not_ a function. If you need to use a function (because the expression is complex, for example) use `$derived.by`.\n\nDeriveds are writable — you can assign to them, just like `$state`, except that they will re-evaluate when their expression changes.\n\nIf the derived expression is an object or array, it will be returned as-is — it is _not_ made deeply reactive. You can, however, use `$state` inside `$derived.by` in the rare cases that you need this.\n\n## `$effect`\n\nEffects are an escape hatch and should mostly be avoided. In particular, avoid updating state inside effects.\n\n- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/attach.md)\n- If you need to run some code in response to user interaction, put the code directly in an event handler or use a [function binding](references/bind.md) as appropriate\n- If you need to log values for debugging purposes, use [`$inspect`](references/inspect.md)\n- If you need to observe something external to Svelte, use [`createSubscriber`](references/svelte-reactivity.md)\n\nNever wrap the contents of an effect in `if (browser) {...}` or similar — effects do not run on the server.\n\n## `$props`\n\nTreat props as though they will change. For example, values that depend on props should usually use `$derived`:\n\n```js\n// @errors: 2451\nlet { type } = $props();\n\n// do this\nlet color = $derived(type === 'danger' ? 'red' : 'green');\n\n// don't do this — `color` will not update if `type` changes\nlet color = type === 'danger' ? 'red' : 'green';\n```\n\n## `$inspect.trace`\n\n`$inspect.trace` is a debugging tool for reactivity. If something is not updating properly or running more than it should you can add `$inspect.trace(label)` as the first line of an `$effect` or `$derived.by` (or any function they call) to trace their dependencies and discover which one triggered an update.\n\n## Events\n\nAny element attribute starting with `on` is treated as an event listener:\n\n```svelte\n<button onclick={() => {...}}>click me</button>\n\n<!-- attribute shorthand also works -->\n<button {onclick}>...</button>\n\n<!-- so do spread attributes -->\n<button {...props}>...</button>\n```\n\nIf you need to attach listeners to `window` or `document` you can use `<svelte:window>` and `<svelte:document>`:\n\n```svelte\n<svelte:window onkeydown={...} />\n<svelte:document onvisibilitychange={...} />\n```\n\nAvoid using `onMount` or `$effect` for this.\n\n## Snippets\n\n[Snippets](references/snippet.md) are a way to define reusable chunks of markup that can be instantiated with the [`{@render ...}`](references/render.md) tag, or passed to components as props. They must be declared within the template.\n\n```svelte\n{#snippet greeting(name)}\n\t<p>hello {name}!</p>\n{/snippet}\n\n{@render greeting('world')}\n```\n\n> [!NOTE] Snippets declared at the top level of a component (i.e. not inside elements or blocks) can be referenced inside `<script>`. A snippet that doesn't reference component state is also available in a `<script module>`, in which case it can be exported for use by other components.\n\n## Each blocks\n\nPrefer to use [keyed each blocks](references/each.md) — this improves performance by allowing Svelte to surgically insert or remove items rather than updating the DOM belonging to existing items.\n\n> [!NOTE] The key _must_ uniquely identify the object. Do not use the index as a key.\n\nAvoid destructuring if you need to mutate the item (with something like `bind:value={item.count}`, for example).\n\n## Using JavaScript variables in CSS\n\nIf you have a JS variable that you want to use inside CSS you can set a CSS custom property with the `style:` directive.\n\n```svelte\n<div style:--columns={columns}>...</div>\n```\n\nYou can then reference `var(--columns)` inside the component's `<style>`.\n\n## Styling child components\n\nThe CSS in a component's `<style>` is scoped to that component. If a parent component needs to control the child's styles, the preferred way is to use CSS custom properties:\n\n```svelte\n<!-- Parent.svelte -->\n<Child --color=\"red\" />\n\n<!-- Child.svelte -->\n<h1>Hello</h1>\n\n<style>\n\th1 {\n\t\tcolor: var(--color);\n\t}\n</style>\n```\n\nIf this is impossible (for example, the child component comes from a library) you can use `:global` to override styles:\n\n```svelte\n<div>\n\t<Child />\n</div>\n\n<style>\n\tdiv :global {\n\t\th1 {\n\t\t\tcolor: red;\n\t\t}\n\t}\n</style>\n```\n\n## Context\n\nConsider using context instead of declaring state in a shared module. This will scope the state to the part of the app that needs it, and eliminate the possibility of it leaking between users when server-side rendering.\n\nUse `createContext` rather than `setContext` and `getContext`, as it provides type safety.\n\n## Async Svelte\n\nIf using version 5.36 or higher, you can use [await expressions](references/await-expressions.md) and [hydratable](references/hydratable.md) to use promises directly inside components. Note that these require the `experimental.async` option to be enabled in `svelte.config.js` as they are not yet considered fully stable.\n\n## Avoid legacy features\n\nAlways use runes mode for new code, and avoid features that have more modern replacements:\n\n- use `$state` instead of implicit reactivity (e.g. `let count = 0; count += 1`)\n- use `$derived` and `$effect` instead of `$:` assignments and statements (but only use effects when there is no better solution)\n- use `$props` instead of `export let`, `$$props` and `$$restProps`\n- use `onclick={...}` instead of `on:click={...}`\n- use `{#snippet ...}` and `{@render ...}` instead of `<slot>` and `$$slots` and `<svelte:fragment>`\n- use `<DynamicComponent>` instead of `<svelte:component this={DynamicComponent}>`\n- use `import Self from './ThisComponent.svelte'` and `<Self>` instead of `<svelte:self>`\n- use classes with `$state` fields to share reactivity between components, instead of using stores\n- use `{@attach ...}` instead of `use:action`\n- use clsx-style arrays and objects in `class` attributes, instead of the `class:` directive\n"
96
+ },
97
+ {
98
+ "name": "svelte-core-bestpractices/references/attach.md",
99
+ "include": [
100
+ "skills"
101
+ ],
102
+ "exclude": [],
103
+ "contents": "Attachments are functions that run in an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt) when an element is mounted to the DOM or when [state](https://svelte.dev/docs/svelte/$state/llms.txt) read inside the function updates.\n\nOptionally, they can return a function that is called before the attachment re-runs, or after the element is later removed from the DOM.\n\n> [!NOTE]\n> Attachments are available in Svelte 5.29 and newer.\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\t/** @type {import('svelte/attachments').Attachment} */\n\tfunction myAttachment(element) {\n\t\tconsole.log(element.nodeName); // 'DIV'\n\n\t\treturn () => {\n\t\t\tconsole.log('cleaning up');\n\t\t};\n\t}\n</script>\n\n<div {@attach myAttachment}>...</div>\n```\n\nAn element can have any number of attachments.\n\n## Attachment factories\n\nA useful pattern is for a function, such as `tooltip` in this example, to _return_ an attachment (demo:\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\timport tippy from 'tippy.js';\n\n\tlet content = $state('Hello!');\n\n\t/**\n\t * @param {string} content\n\t * @returns {import('svelte/attachments').Attachment}\n\t */\n\tfunction tooltip(content) {\n\t\treturn (element) => {\n\t\t\tconst tooltip = tippy(element, { content });\n\t\t\treturn tooltip.destroy;\n\t\t};\n\t}\n</script>\n\n<input bind:value={content} />\n\n<button {@attach tooltip(content)}>\n\tHover me\n</button>\n```\n\nSince the `tooltip(content)` expression runs inside an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt), the attachment will be destroyed and recreated whenever `content` changes. The same thing would happen for any state read _inside_ the attachment function when it first runs. (If this isn't what you want, see [Controlling when attachments re-run](#Controlling-when-attachments-re-run).)\n\n## Inline attachments\n\nAttachments can also be created inline (demo:\n\n```svelte\n<!--- file: App.svelte --->\n<canvas\n\twidth={32}\n\theight={32}\n\t{@attach (canvas) => {\n\t\tconst context = canvas.getContext('2d');\n\n\t\t$effect(() => {\n\t\t\tcontext.fillStyle = color;\n\t\t\tcontext.fillRect(0, 0, canvas.width, canvas.height);\n\t\t});\n\t}}\n></canvas>\n```\n\n> [!NOTE]\n> The nested effect runs whenever `color` changes, while the outer effect (where `canvas.getContext(...)` is called) only runs once, since it doesn't read any reactive state.\n\n## Conditional attachments\n\nFalsy values like `false` or `undefined` are treated as no attachment, enabling conditional usage:\n\n```svelte\n<div {@attach enabled && myAttachment}>...</div>\n```\n\n## Passing attachments to components\n\nWhen used on a component, `{@attach ...}` will create a prop whose key is a [`Symbol`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol). If the component then [spreads](https://svelte.dev/tutorial/svelte/spread-props/llms.txt) props onto an element, the element will receive those attachments.\n\nThis allows you to create _wrapper components_ that augment elements (demo:\n\n```svelte\n<!--- file: Button.svelte --->\n<script>\n\t/** @type {import('svelte/elements').HTMLButtonAttributes} */\n\tlet { children, ...props } = $props();\n</script>\n\n<!-- `props` includes attachments -->\n<button {...props}>\n\t{@render children?.()}\n</button>\n```\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\timport tippy from 'tippy.js';\n\timport Button from './Button.svelte';\n\n\tlet content = $state('Hello!');\n\n\t/**\n\t * @param {string} content\n\t * @returns {import('svelte/attachments').Attachment}\n\t */\n\tfunction tooltip(content) {\n\t\treturn (element) => {\n\t\t\tconst tooltip = tippy(element, { content });\n\t\t\treturn tooltip.destroy;\n\t\t};\n\t}\n</script>\n\n<input bind:value={content} />\n\n<Button {@attach tooltip(content)}>\n\tHover me\n</Button>\n```\n\n## Controlling when attachments re-run\n\nAttachments, unlike [actions](https://svelte.dev/docs/svelte/use/llms.txt), are fully reactive: `{@attach foo(bar)}` will re-run on changes to `foo` _or_ `bar` (or any state read inside `foo`):\n\n```js\n// @errors: 7006 2304 2552\nfunction foo(bar) {\n\treturn (node) => {\n\t\tveryExpensiveSetupWork(node);\n\t\tupdate(node, bar);\n\t};\n}\n```\n\nIn the rare case that this is a problem (for example, if `foo` does expensive and unavoidable setup work) consider passing the data inside a function and reading it in a child effect:\n\n```js\n// @errors: 7006 2304 2552\nfunction foo(+++getBar+++) {\n\treturn (node) => {\n\t\tveryExpensiveSetupWork(node);\n\n+++\t\t$effect(() => {\n\t\t\tupdate(node, getBar());\n\t\t});+++\n\t}\n}\n```\n\n## Creating attachments programmatically\n\nTo add attachments to an object that will be spread onto a component or element, use [`createAttachmentKey`](https://svelte.dev/docs/svelte/svelte-attachments/llms.txt#createAttachmentKey).\n\n## Converting actions to attachments\n\nIf you're using a library that only provides actions, you can convert them to attachments with [`fromAction`](https://svelte.dev/docs/svelte/svelte-attachments/llms.txt#fromAction), allowing you to (for example) use them with components.\n"
104
+ },
105
+ {
106
+ "name": "svelte-core-bestpractices/references/await-expressions.md",
107
+ "include": [
108
+ "skills"
109
+ ],
110
+ "exclude": [],
111
+ "contents": "As of Svelte 5.36, you can use the `await` keyword inside your components in three places where it was previously unavailable:\n\n- at the top level of your component's `<script>`\n- inside `$derived(...)` declarations\n- inside your markup\n\nThis feature is currently experimental, and you must opt in by adding the `experimental.async` option wherever you [configure](https://svelte.dev/docs/kit/configuration/llms.txt) Svelte, usually `svelte.config.js`:\n\n```js\n/// file: svelte.config.js\nexport default {\n\tcompilerOptions: {\n\t\texperimental: {\n\t\t\tasync: true\n\t\t}\n\t}\n};\n```\n\nThe experimental flag will be removed in Svelte 6.\n\n## Synchronized updates\n\nWhen an `await` expression depends on a particular piece of state, changes to that state will not be reflected in the UI until the asynchronous work has completed, so that the UI is not left in an inconsistent state. In other words, in an example like this...\n\n<!-- codeblock:start {\"title\":\"Synchronized updates\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\tlet a = $state(1);\n\tlet b = $state(2);\n\n\tasync function add(a, b) {\n\t\tawait new Promise((f) => setTimeout(f, 500)); // artificial delay\n\t\treturn a + b;\n\t}\n</script>\n\n<input type=\"number\" bind:value={a}>\n<input type=\"number\" bind:value={b}>\n\n<p>{a} + {b} = {await add(a, b)}</p>\n```\n\n<!-- codeblock:end -->\n\n...if you increment `a`, the contents of the `<p>` will _not_ immediately update to read this —\n\n```html\n<p>2 + 2 = 3</p>\n```\n\n— instead, the text will update to `2 + 2 = 4` when `add(a, b)` resolves.\n\nUpdates can overlap — a fast update will be reflected in the UI while an earlier slow update is still ongoing.\n\n## Concurrency\n\nSvelte will do as much asynchronous work as it can in parallel. For example if you have two `await` expressions in your markup...\n\n```svelte\n<p>{await one(x)}</p>\n<p>{await two(y)}</p>\n```\n\n...both functions will run at the same time, as they are independent expressions, even though they are _visually_ sequential.\n\nThis does not apply to sequential `await` expressions inside your `<script>` or inside async functions — these run like any other asynchronous JavaScript. An exception is that independent `$derived` expressions will update independently, even though they will run sequentially when they are first created:\n\n```js\n// `b` will not be created until `a` has resolved,\n// but once created they will update independently\n// even if `x` and `y` update simultaneously\nlet a = $derived(await one(x));\nlet b = $derived(await two(y));\n```\n\n> [!NOTE] If you write code like this, expect Svelte to give you an [`await_waterfall`](https://svelte.dev/docs/svelte/runtime-warnings/llms.txt#Client-warnings-await_waterfall) warning\n\n## Indicating loading states\n\nTo render placeholder UI, you can wrap content in a `<svelte:boundary>` with a [`pending`](https://svelte.dev/docs/svelte/svelte-boundary/llms.txt#Properties-pending) snippet. This will be shown when the boundary is first created, but not for subsequent updates, which are globally coordinated.\n\nAfter the contents of a boundary have resolved for the first time and have replaced the `pending` snippet, you can detect subsequent async work with [`$effect.pending()`](https://svelte.dev/docs/svelte/$effect/llms.txt#$effect.pending). This is what you would use to display a \"we're asynchronously validating your input\" spinner next to a form field, for example.\n\nYou can also use [`settled()`](https://svelte.dev/docs/svelte/svelte/llms.txt#settled) to get a promise that resolves when the current update is complete:\n\n```js\nimport { tick, settled } from 'svelte';\n\nasync function onclick() {\n\tupdating = true;\n\n\t// without this, the change to `updating` will be\n\t// grouped with the other changes, meaning it\n\t// won't be reflected in the UI\n\tawait tick();\n\n\tcolor = 'octarine';\n\tanswer = 42;\n\n\tawait settled();\n\n\t// any updates affected by `color` or `answer`\n\t// have now been applied\n\tupdating = false;\n}\n```\n\n## Error handling\n\nErrors in `await` expressions will bubble to the nearest [error boundary](https://svelte.dev/docs/svelte/svelte-boundary/llms.txt).\n\n## Server-side rendering\n\nSvelte supports asynchronous server-side rendering (SSR) with the `render(...)` API. To use it, simply await the return value:\n\n```js\n/// file: server.js\nimport { render } from 'svelte/server';\nimport App from './App.svelte';\n\nconst { head, body } = +++await+++ render(App);\n```\n\n> [!NOTE] If you're using a framework like SvelteKit, this is done on your behalf.\n\nIf a `<svelte:boundary>` with a `pending` snippet is encountered during SSR, that snippet will be rendered while the rest of the content is ignored. All `await` expressions encountered outside boundaries with `pending` snippets will resolve and render their contents prior to `await render(...)` returning.\n\n> [!NOTE] In the future, we plan to add a streaming implementation that renders the content in the background.\n\n## Forking\n\nThe [`fork(...)`](https://svelte.dev/docs/svelte/svelte/llms.txt#fork) API, added in 5.42, makes it possible to run `await` expressions that you _expect_ to happen in the near future. This is mainly intended for frameworks like SvelteKit to implement preloading when (for example) users signal an intent to navigate.\n\n```svelte\n<script>\n\timport { fork } from 'svelte';\n\timport Menu from './Menu.svelte';\n\n\tlet open = $state(false);\n\n\t/** @type {import('svelte').Fork | null} */\n\tlet pending = null;\n\n\tfunction preload() {\n\t\tpending ??= fork(() => {\n\t\t\topen = true;\n\t\t});\n\t}\n\n\tfunction discard() {\n\t\tpending?.discard();\n\t\tpending = null;\n\t}\n</script>\n\n<button\n\tonfocusin={preload}\n\tonfocusout={discard}\n\tonpointerenter={preload}\n\tonpointerleave={discard}\n\tonclick={() => {\n\t\tpending?.commit();\n\t\tpending = null;\n\n\t\t// in case `pending` didn't exist\n\t\t// (if it did, this is a no-op)\n\t\topen = true;\n\t}}\n>open menu</button>\n\n{#if open}\n\t<!-- any async work inside this component will start\n\t as soon as the fork is created -->\n\t<Menu onclose={() => open = false} />\n{/if}\n```\n\n## Caveats\n\nAs an experimental feature, the details of how `await` is handled (and related APIs like `$effect.pending()`) are subject to breaking changes outside of a semver major release, though we intend to keep such changes to a bare minimum.\n\n## Breaking changes\n\nEffects run in a slightly different order when the `experimental.async` option is `true`. Specifically, _block_ effects like `{#if ...}` and `{#each ...}` now run before an `$effect.pre` or `beforeUpdate` in the same component, which means that in very rare situations.\n"
112
+ },
113
+ {
114
+ "name": "svelte-core-bestpractices/references/bind.md",
115
+ "include": [
116
+ "skills"
117
+ ],
118
+ "exclude": [],
119
+ "contents": "## Function bindings\n\nYou can also use `bind:property={get, set}`, where `get` and `set` are functions, allowing you to perform validation and transformation:\n\n```svelte\n<input bind:value={\n\t() => value,\n\t(v) => value = v.toLowerCase()}\n/>\n```\n\nIn the case of readonly bindings like [dimension bindings](#Dimensions), the `get` value should be `null`:\n\n```svelte\n<div\n\tbind:clientWidth={null, redraw}\n\tbind:clientHeight={null, redraw}\n>...</div>\n```\n\n> [!NOTE]\n> Function bindings are available in Svelte 5.9.0 and newer.\n"
120
+ },
121
+ {
122
+ "name": "svelte-core-bestpractices/references/each.md",
123
+ "include": [
124
+ "skills"
125
+ ],
126
+ "exclude": [],
127
+ "contents": "## Keyed each blocks\n\n```svelte\n<!--- copy: false --->\n{#each expression as name (key)}...{/each}\n```\n\n```svelte\n<!--- copy: false --->\n{#each expression as name, index (key)}...{/each}\n```\n\nIf a _key_ expression is provided — which must uniquely identify each list item — Svelte will use it to intelligently update the list when data changes by inserting, moving and deleting items, rather than adding or removing items at the end and updating the state in the middle.\n\nThe key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change.\n\n```svelte\n{#each items as item (item.id)}\n\t<li>{item.name} x {item.qty}</li>\n{/each}\n\n<!-- or with additional index value -->\n{#each items as item, i (item.id)}\n\t<li>{i + 1}: {item.name} x {item.qty}</li>\n{/each}\n```\n\nYou can freely use destructuring and rest patterns in each blocks.\n\n```svelte\n{#each items as { id, name, qty }, i (id)}\n\t<li>{i + 1}: {name} x {qty}</li>\n{/each}\n\n{#each objects as { id, ...rest }}\n\t<li><span>{id}</span><MyComponent {...rest} /></li>\n{/each}\n\n{#each items as [id, ...rest]}\n\t<li><span>{id}</span><MyComponent values={rest} /></li>\n{/each}\n```\n"
128
+ },
129
+ {
130
+ "name": "svelte-core-bestpractices/references/hydratable.md",
131
+ "include": [
132
+ "skills"
133
+ ],
134
+ "exclude": [],
135
+ "contents": "In Svelte, when you want to render asynchronous content data on the server, you can simply `await` it. This is great! However, it comes with a pitfall: when hydrating that content on the client, Svelte has to redo the asynchronous work, which blocks hydration for however long it takes:\n\n```svelte\n<script>\n import { getUser } from 'my-database-library';\n\n // This will get the user on the server, render the user's name into the h1,\n // and then, during hydration on the client, it will get the user _again_,\n // blocking hydration until it's done.\n const user = await getUser();\n</script>\n\n<h1>{user.name}</h1>\n```\n\nThat's silly, though. If we've already done the hard work of getting the data on the server, we don't want to get it again during hydration on the client. `hydratable` is a low-level API built to solve this problem. You probably won't need this very often — it will be used behind the scenes by whatever datafetching library you use. For example, it powers [remote functions in SvelteKit](https://svelte.dev/docs/kit/remote-functions/llms.txt).\n\nTo fix the example above:\n\n```svelte\n<script>\n import { hydratable } from 'svelte';\n import { getUser } from 'my-database-library';\n\n // During server rendering, this will serialize and stash the result of `getUser`, associating\n // it with the provided key and baking it into the `head` content. During hydration, it will\n // look for the serialized version, returning it instead of running `getUser`. After hydration\n // is done, if it's called again, it'll simply invoke `getUser`.\n const user = await hydratable('user', () => getUser());\n</script>\n\n<h1>{user.name}</h1>\n```\n\nThis API can also be used to provide access to random or time-based values that are stable between server rendering and hydration. For example, to get a random number that doesn't update on hydration:\n\n```ts\nimport { hydratable } from 'svelte';\nconst rand = hydratable('random', () => Math.random());\n```\n\nIf you're a library author, be sure to prefix the keys of your `hydratable` values with the name of your library so that your keys don't conflict with other libraries.\n\n## Serialization\n\nAll data returned from a `hydratable` function must be serializable. But this doesn't mean you're limited to JSON — Svelte uses [`devalue`](https://npmjs.com/package/devalue), which can serialize all sorts of things including `Map`, `Set`, `URL`, and `BigInt`. Check the documentation page for a full list. In addition to these, thanks to some Svelte magic, you can also fearlessly use promises:\n\n```svelte\n<script>\n import { hydratable } from 'svelte';\n const promises = hydratable('random', () => {\n return {\n one: Promise.resolve(1),\n two: Promise.resolve(2)\n }\n });\n</script>\n\n{await promises.one}\n{await promises.two}\n```\n\n## CSP\n\n`hydratable` adds an inline `<script>` block to the `head` returned from `render`. If you're using [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) (CSP), this script will likely fail to run. You can provide a `nonce` to `render`:\n\n```js\nconst nonce = crypto.randomUUID();\n\nconst { head, body } = await render(App, {\n\tcsp: { nonce }\n});\n```\n\nThis will add the `nonce` to the script block, on the assumption that you will later add the same nonce to the CSP header of the document that contains it:\n\n```js\nresponse.headers.set(\n 'Content-Security-Policy',\n `script-src 'nonce-${nonce}'`\n );\n```\n\nIt's essential that a `nonce` — which, British slang definition aside, means 'number used once' — is only used when dynamically server rendering an individual response.\n\nIf instead you are generating static HTML ahead of time, you must use hashes instead:\n\n```js\nconst { head, body, hashes } = await render(App, {\n\tcsp: { hash: true }\n});\n```\n\n`hashes.script` will be an array of strings like `[\"sha256-abcd123\"]`. As with `nonce`, the hashes should be used in your CSP header:\n\n```js\nresponse.headers.set(\n 'Content-Security-Policy',\n `script-src ${hashes.script.map((hash) => `'${hash}'`).join(' ')}`\n );\n```\n\nWe recommend using `nonce` over hash if you can, as `hash` will interfere with streaming SSR in the future.\n"
136
+ },
137
+ {
138
+ "name": "svelte-core-bestpractices/references/inspect.md",
139
+ "include": [
140
+ "skills"
141
+ ],
142
+ "exclude": [],
143
+ "contents": "> [!NOTE] `$inspect` only works during development. In a production build it becomes a noop.\n\nThe `$inspect` rune is roughly equivalent to `console.log`, with the exception that it will re-run whenever its argument changes. `$inspect` tracks reactive state deeply, meaning that updating something inside an object or array using fine-grained reactivity will cause it to re-fire:\n\n<!-- codeblock:start {\"title\":\"$inspect(...)\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\tlet count = $state(0);\n\tlet message = $state('hello');\n\n\t$inspect(count, message); // will console.log when `count` or `message` change\n</script>\n\n<button onclick={() => count++}>Increment</button>\n<input bind:value={message} />\n```\n\n<!-- codeblock:end -->\n\nOn updates, a stack trace will be printed, making it easy to find the origin of a state change (unless you're in the playground, due to technical limitations).\n\n## $inspect(...).with\n\n`$inspect(...)` returns an object with a `with` method, which you can invoke with a callback that will then be invoked instead of `console.log`. The first argument to the callback is either `\"init\"` or `\"update\"`; subsequent arguments are the values passed to `$inspect`:\n\n<!-- codeblock:start {\"title\":\"$inspect(...).with(...)\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\tlet count = $state(0);\n\n\t$inspect(count).with((type, count) => {\n\t\tif (type === 'update') {\n\t\t\tdebugger; // or `console.trace`, or whatever you want\n\t\t}\n\t});\n</script>\n\n<button onclick={() => count++}>Increment</button>\n```\n\n<!-- codeblock:end -->\n\n## $inspect.trace(...)\n\nThis rune, added in 5.14, causes the surrounding function to be _traced_ in development. Any time the function re-runs as part of an [effect](https://svelte.dev/docs/svelte/$effect/llms.txt) or a [derived](https://svelte.dev/docs/svelte/$derived/llms.txt), information will be printed to the console about which pieces of reactive state caused the effect to fire.\n\n```svelte\n<script>\n\timport { doSomeWork } from './elsewhere';\n\n\t$effect(() => {\n\t\t+++// $inspect.trace must be the first statement of a function body+++\n\t\t+++$inspect.trace();+++\n\t\tdoSomeWork();\n\t});\n</script>\n```\n\n`$inspect.trace` takes an optional first argument which will be used as the label.\n"
144
+ },
145
+ {
146
+ "name": "svelte-core-bestpractices/references/render.md",
147
+ "include": [
148
+ "skills"
149
+ ],
150
+ "exclude": [],
151
+ "contents": "To render a [snippet](https://svelte.dev/docs/svelte/snippet/llms.txt), use a `{@render ...}` tag.\n\n```svelte\n{#snippet sum(a, b)}\n\t<p>{a} + {b} = {a + b}</p>\n{/snippet}\n\n{@render sum(1, 2)}\n{@render sum(3, 4)}\n{@render sum(5, 6)}\n```\n\nThe expression can be an identifier like `sum`, or an arbitrary JavaScript expression:\n\n```svelte\n{@render (cool ? coolSnippet : lameSnippet)()}\n```\n\n## Optional snippets\n\nIf the snippet is potentially undefined — for example, because it's an incoming prop — then you can use optional chaining to only render it when it _is_ defined:\n\n```svelte\n{@render children?.()}\n```\n\nAlternatively, use an [`{#if ...}`](https://svelte.dev/docs/svelte/if/llms.txt) block with an `:else` clause to render fallback content:\n\n```svelte\n{#if children}\n\t{@render children()}\n{:else}\n\t<p>fallback content</p>\n{/if}\n```\n"
152
+ },
153
+ {
154
+ "name": "svelte-core-bestpractices/references/snippet.md",
155
+ "include": [
156
+ "skills"
157
+ ],
158
+ "exclude": [],
159
+ "contents": "```svelte\n<!--- copy: false --->\n{#snippet name()}...{/snippet}\n```\n\n```svelte\n<!--- copy: false --->\n{#snippet name(param1, param2, paramN)}...{/snippet}\n```\n\nSnippets, and [render tags](https://svelte.dev/docs/svelte/@render/llms.txt), are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like this...\n\n```svelte\n{#each images as image}\n\t{#if image.href}\n\t\t<a href={image.href}>\n\t\t\t<figure>\n\t\t\t\t<img src={image.src} alt={image.caption} width={image.width} height={image.height} />\n\t\t\t\t<figcaption>{image.caption}</figcaption>\n\t\t\t</figure>\n\t\t</a>\n\t{:else}\n\t\t<figure>\n\t\t\t<img src={image.src} alt={image.caption} width={image.width} height={image.height} />\n\t\t\t<figcaption>{image.caption}</figcaption>\n\t\t</figure>\n\t{/if}\n{/each}\n```\n\n...you can write this:\n\n```svelte\n{#snippet figure(image)}\n\t<figure>\n\t\t<img src={image.src} alt={image.caption} width={image.width} height={image.height} />\n\t\t<figcaption>{image.caption}</figcaption>\n\t</figure>\n{/snippet}\n\n{#each images as image}\n\t{#if image.href}\n\t\t<a href={image.href}>\n\t\t\t{@render figure(image)}\n\t\t</a>\n\t{:else}\n\t\t{@render figure(image)}\n\t{/if}\n{/each}\n```\n\nLike function declarations, snippets can have an arbitrary number of parameters, which can have default values, and you can destructure each parameter. You cannot use rest parameters, however.\n\n## Snippet scope\n\nSnippets can be declared anywhere inside your component. They can reference values declared outside themselves, for example in the `<script>` tag or in `{#each ...}` blocks...\n\n<!-- codeblock:start {\"title\":\"Snippets\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\tlet { message = `it's great to see you!` } = $props();\n</script>\n\n{#snippet hello(name)}\n\t<p>hello {name}! {message}!</p>\n{/snippet}\n\n{@render hello('alice')}\n{@render hello('bob')}\n```\n\n<!-- codeblock:end -->\n\n...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings):\n\n```svelte\n<div>\n\t{#snippet x()}\n\t\t{#snippet y()}...{/snippet}\n\n\t\t<!-- this is fine -->\n\t\t{@render y()}\n\t{/snippet}\n\n\t<!-- this will error, as `y` is not in scope -->\n\t{@render y()}\n</div>\n\n<!-- this will also error, as `x` is not in scope -->\n{@render x()}\n```\n\nSnippets can reference themselves and each other:\n\n<!-- codeblock:start {\"title\":\"Self-referencing snippets\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n{#snippet blastoff()}\n\t<span>🚀</span>\n{/snippet}\n\n{#snippet countdown(n)}\n\t{#if n > 0}\n\t\t<span>{n}...</span>\n\t\t{@render countdown(n - 1)}\n\t{:else}\n\t\t{@render blastoff()}\n\t{/if}\n{/snippet}\n\n{@render countdown(10)}\n```\n\n<!-- codeblock:end -->\n\n## Passing snippets to components\n\n### Explicit props\n\nWithin the template, snippets are values just like any other. As such, they can be passed to components as props:\n\n<!-- codeblock:start {\"title\":\"Explicit snippet props\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\timport Table from './Table.svelte';\n\n\tconst fruits = [\n\t\t{ name: 'apples', qty: 5, price: 2 },\n\t\t{ name: 'bananas', qty: 10, price: 1 },\n\t\t{ name: 'cherries', qty: 20, price: 0.5 }\n\t];\n</script>\n\n{#snippet header()}\n\t<th>fruit</th>\n\t<th>qty</th>\n\t<th>price</th>\n\t<th>total</th>\n{/snippet}\n\n{#snippet row(d)}\n\t<td>{d.name}</td>\n\t<td>{d.qty}</td>\n\t<td>{d.price}</td>\n\t<td>{d.qty * d.price}</td>\n{/snippet}\n\n<Table data={fruits} +++{header} {row}+++ />\n```\n\n```svelte\n<!--- file: Table.svelte --->\n<script>\n\tlet { data, header, row } = $props();\n</script>\n\n<table>\n\t{#if header}\n\t\t<thead>\n\t\t\t<tr>{@render header()}</tr>\n\t\t</thead>\n\t{/if}\n\n\t<tbody>\n\t\t{#each data as d}\n\t\t\t<tr>{@render row(d)}</tr>\n\t\t{/each}\n\t</tbody>\n</table>\n\n<style>\n\ttable {\n\t\ttext-align: left;\n\t\tborder-spacing: 0;\n\t}\n\n\ttbody tr:nth-child(2n+1) {\n\t\tbackground: ButtonFace;\n\t}\n\n\ttable :global(th), table :global(td) {\n\t\tpadding: 0.5em;\n\t}\n</style>\n```\n\n<!-- codeblock:end -->\n\nThink about it like passing content instead of data to a component. The concept is similar to slots in web components.\n\n### Implicit props\n\nAs an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component:\n\n<!-- codeblock:start {\"title\":\"Implicit snippet props\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\timport Table from './Table.svelte';\n\n\tconst fruits = [\n\t\t{ name: 'apples', qty: 5, price: 2 },\n\t\t{ name: 'bananas', qty: 10, price: 1 },\n\t\t{ name: 'cherries', qty: 20, price: 0.5 }\n\t];\n</script>\n\n<Table data={fruits}>\n\t{#snippet header()}\n\t\t<th>fruit</th>\n\t\t<th>qty</th>\n\t\t<th>price</th>\n\t\t<th>total</th>\n\t{/snippet}\n\n\t{#snippet row(d)}\n\t\t<td>{d.name}</td>\n\t\t<td>{d.qty}</td>\n\t\t<td>{d.price}</td>\n\t\t<td>{d.qty * d.price}</td>\n\t{/snippet}\n</Table>\n```\n\n```svelte\n<!--- file: Table.svelte --->\n<script>\n\tlet { data, header, row } = $props();\n</script>\n\n<table>\n\t{#if header}\n\t\t<thead>\n\t\t\t<tr>{@render header()}</tr>\n\t\t</thead>\n\t{/if}\n\n\t<tbody>\n\t\t{#each data as d}\n\t\t\t<tr>{@render row(d)}</tr>\n\t\t{/each}\n\t</tbody>\n</table>\n\n<style>\n\ttable {\n\t\ttext-align: left;\n\t\tborder-spacing: 0;\n\t}\n\n\ttbody tr:nth-child(2n+1) {\n\t\tbackground: ButtonFace;\n\t}\n\n\ttable :global(th), table :global(td) {\n\t\tpadding: 0.5em;\n\t}\n</style>\n```\n\n<!-- codeblock:end -->\n\n### Implicit `children` snippet\n\nAny content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet:\n\n<!-- codeblock:start {\"title\":\"Implicit children snippet\",\"selected\":\"Button.svelte\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\timport Button from './Button.svelte';\n</script>\n\n<Button>click me</Button>\n```\n\n```svelte\n<!--- file: Button.svelte --->\n<script>\n\tlet { children } = $props();\n</script>\n\n<!-- result will be <button>click me</button> -->\n<button>{@render children()}</button>\n```\n\n<!-- codeblock:end -->\n\n> [!NOTE] Note that you cannot have a prop called `children` if you also have content inside the component — for this reason, you should avoid having props with that name\n\n### Optional snippet props\n\nYou can declare snippet props as being optional. You can either use optional chaining to not render anything if the snippet isn't set...\n\n```svelte\n<script>\n let { children } = $props();\n</script>\n\n{@render children?.()}\n```\n\n...or use an `#if` block to render fallback content:\n\n```svelte\n<script>\n let { children } = $props();\n</script>\n\n{#if children}\n {@render children()}\n{:else}\n fallback content\n{/if}\n```\n\n## Typing snippets\n\nSnippets implement the `Snippet` interface imported from `'svelte'`:\n\n```svelte\n<script lang=\"ts\">\n\timport type { Snippet } from 'svelte';\n\n\tinterface Props {\n\t\tdata: any[];\n\t\tchildren: Snippet;\n\t\trow: Snippet<[any]>;\n\t}\n\n\tlet { data, children, row }: Props = $props();\n</script>\n```\n\nWith this change, red squigglies will appear if you try and use the component without providing a `data` prop and a `row` snippet. Notice that the type argument provided to `Snippet` is a tuple, since snippets can have multiple parameters.\n\nWe can tighten things up further by declaring a generic, so that `data` and `row` refer to the same type:\n\n```svelte\n<script lang=\"ts\" generics=\"T\">\n\timport type { Snippet } from 'svelte';\n\n\tlet {\n\t\tdata,\n\t\tchildren,\n\t\trow\n\t}: {\n\t\tdata: T[];\n\t\tchildren: Snippet;\n\t\trow: Snippet<[T]>;\n\t} = $props();\n</script>\n```\n\n## Exporting snippets\n\nSnippets declared at the top level of a `.svelte` file can be exported from a `<script module>` for use in other components, provided they don't reference any declarations in a non-module `<script>` (whether directly or indirectly, via other snippets):\n\n<!-- codeblock:start {\"title\":\"Exported snippets\",\"selected\":\"snippets.svelte\"} -->\n\n```svelte\n<!--- file: App.svelte --->\n<script>\n\timport { add } from './snippets.svelte';\n</script>\n\n{@render add(1, 2)}\n\n```\n\n```svelte\n<!--- file: snippets.svelte --->\n<script module>\n\texport { add };\n</script>\n\n{#snippet add(a, b)}\n\t{a} + {b} = {a + b}\n{/snippet}\n```\n\n<!-- codeblock:end -->\n\n> [!NOTE]\n> This requires Svelte 5.5.0 or newer\n\n## Programmatic snippets\n\nSnippets can be created programmatically with the [`createRawSnippet`](https://svelte.dev/docs/svelte/svelte/llms.txt#createRawSnippet) API. This is intended for advanced use cases.\n\n## Snippets and slots\n\nIn Svelte 4, content can be passed to components using [slots](https://svelte.dev/docs/svelte/legacy-slots/llms.txt). Snippets are more powerful and flexible, and so slots have been deprecated in Svelte 5.\n"
160
+ },
161
+ {
162
+ "name": "svelte-core-bestpractices/references/svelte-reactivity.md",
163
+ "include": [
164
+ "skills"
165
+ ],
166
+ "exclude": [],
167
+ "contents": "## createSubscriber\n\n<blockquote class=\"since note\">\n\nAvailable since 5.7.0\n\n</blockquote>\n\nReturns a `subscribe` function that integrates external event-based systems with Svelte's reactivity.\nIt's particularly useful for integrating with web APIs like `MediaQuery`, `IntersectionObserver`, or `WebSocket`.\n\nIf `subscribe` is called inside an effect (including indirectly, for example inside a getter),\nthe `start` callback will be called with an `update` function. Whenever `update` is called, the effect re-runs.\n\nIf `start` returns a cleanup function, it will be called when the effect is destroyed.\n\nIf `subscribe` is called in multiple effects, `start` will only be called once as long as the effects\nare active, and the returned teardown function will only be called when all effects are destroyed.\n\nIt's best understood with an example. Here's an implementation of [`MediaQuery`](https://svelte.dev/docs/svelte/svelte-reactivity/llms.txt#MediaQuery):\n\n```js\n// @errors: 7031\nimport { createSubscriber } from 'svelte/reactivity';\nimport { on } from 'svelte/events';\n\nexport class MediaQuery {\n\t#query;\n\t#subscribe;\n\n\tconstructor(query) {\n\t\tthis.#query = window.matchMedia(`(${query})`);\n\n\t\tthis.#subscribe = createSubscriber((update) => {\n\t\t\t// when the `change` event occurs, re-run any effects that read `this.current`\n\t\t\tconst off = on(this.#query, 'change', update);\n\n\t\t\t// stop listening when all the effects are destroyed\n\t\t\treturn () => off();\n\t\t});\n\t}\n\n\tget current() {\n\t\t// This makes the getter reactive, if read in an effect\n\t\tthis.#subscribe();\n\n\t\t// Return the current state of the query, whether or not we're in an effect\n\t\treturn this.#query.matches;\n\t}\n}\n```\n\n<div class=\"ts-block\">\n\n```dts\nfunction createSubscriber(\n\tstart: (update: () => void) => (() => void) | void\n): () => void;\n```\n\n</div>\n"
168
+ },
73
169
  {
74
170
  "name": "package.json",
75
171
  "include": [
@@ -1,4 +1,4 @@
1
- import { A as StringQuestion, C as BooleanQuestion, D as OptionValues, E as OptionDefinition, O as Question, S as BaseQuestion, T as NumberQuestion, _ as SvApi, a as Addon, b as Workspace, c as AddonReference, d as ConditionDefinition, f as ConfiguredAddon, g as SetupResult, h as PreparedAddon, i as add, j as officialAddons, k as SelectQuestion, l as AddonResult, m as OptionBuilder, n as InstallOptions, o as AddonDefinition, p as LoadedAddon, r as OptionMap, s as AddonInput, t as AddonMap, u as AddonSource, v as defineAddon, w as MultiSelectQuestion, x as WorkspaceOptions, y as defineAddonOptions } from "../engine-BhR6Vrxy.mjs";
1
+ import { A as StringQuestion, C as BooleanQuestion, D as OptionValues, E as OptionDefinition, O as Question, S as BaseQuestion, T as NumberQuestion, _ as SvApi, a as Addon, b as Workspace, c as AddonReference, d as ConditionDefinition, f as ConfiguredAddon, g as SetupResult, h as PreparedAddon, i as add, j as officialAddons, k as SelectQuestion, l as AddonResult, m as OptionBuilder, n as InstallOptions, o as AddonDefinition, p as LoadedAddon, r as OptionMap, s as AddonInput, t as AddonMap, u as AddonSource, v as defineAddon, w as MultiSelectQuestion, x as WorkspaceOptions, y as defineAddonOptions } from "../engine-5Sg3lJiQ.mjs";
2
2
 
3
3
  //#region src/create/index.d.ts
4
4
  type TemplateType = (typeof templateTypes)[number];
@@ -1,4 +1,4 @@
1
- import { c as defineAddonOptions, m as create$1, o as officialAddons, s as defineAddon, t as add, y as svDeprecated } from "../engine-DbEL5s1q.mjs";
1
+ import { c as defineAddonOptions, m as create$1, o as officialAddons, s as defineAddon, t as add, y as svDeprecated } from "../engine-B7chsCPG.mjs";
2
2
  //#region src/index.ts
3
3
  function create(cwdOrOptions, legacyOptions) {
4
4
  if (typeof cwdOrOptions === "string") {
@@ -1,4 +1,4 @@
1
- import { r as OptionMap, t as AddonMap } from "../engine-BhR6Vrxy.mjs";
1
+ import { r as OptionMap, t as AddonMap } from "../engine-5Sg3lJiQ.mjs";
2
2
  import { Page } from "@playwright/test";
3
3
  import { TestProject } from "vitest/node";
4
4
 
@@ -1,4 +1,4 @@
1
- import { S as __toESM, b as __commonJSMin, f as R, i as addPnpmAllowBuilds, m as create, p as z, t as add, x as __require } from "../engine-DbEL5s1q.mjs";
1
+ import { S as __toESM, b as __commonJSMin, f as R, i as addPnpmAllowBuilds, m as create, p as z, t as add, x as __require } from "../engine-B7chsCPG.mjs";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import V from "node:process";
@@ -22,11 +22,11 @@
22
22
  "access": "public"
23
23
  },
24
24
  "peerDependencies": {
25
- "sv": "^0.16.6"
25
+ "sv": "^0.17.0"
26
26
  },
27
27
  "devDependencies": {
28
- "sv": "^0.16.6",
29
- "@sveltejs/sv-utils": "^0.3.2",
28
+ "sv": "^0.17.0",
29
+ "@sveltejs/sv-utils": "^0.3.3",
30
30
  "@playwright/test": "^1.60.0",
31
31
  "@types/node": "^25.9.1",
32
32
  "tsdown": "^0.22.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sv",
3
- "version": "0.16.6",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "description": "A command line interface (CLI) for creating and maintaining Svelte applications",
6
6
  "license": "MIT",
@@ -25,7 +25,7 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "@sveltejs/sv-utils": "0.3.2"
28
+ "@sveltejs/sv-utils": "0.3.3"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@clack/prompts": "1.5.0",