diff --git a/UbikLink.AppHost/Program.cs b/UbikLink.AppHost/Program.cs
index 9715d2d..5996885 100644
--- a/UbikLink.AppHost/Program.cs
+++ b/UbikLink.AppHost/Program.cs
@@ -19,6 +19,7 @@
var authTokenStoreKey = builder.AddParameter("auth-token-store-key", secret: true);
var authRegisterAuthorizationKey = builder.AddParameter("auth-register-authorization-key", secret: true);
var emailActivationEnable = builder.AddParameter("email-activation-enable", secret: false);
+var hubSignSecureKey = builder.AddParameter("hub-signtoken-key", secret: true);
//Postgres (local)
var db = builder.AddPostgres("ubiklink-postgres", postgresUsername, postgresPassword)
@@ -56,12 +57,21 @@
.WithEnvironment("Messaging__RabbitUser", rabbitUser)
.WithEnvironment("Messaging__RabbitPassword", rabbitPassword)
.WithEnvironment("AuthRegister__Key", authRegisterAuthorizationKey)
+ .WithEnvironment("AuthRegister__HubSignSecureKey", hubSignSecureKey)
.WithEnvironment("AuthRegister__EmailActivationActivated", emailActivationEnable)
.WithReference(securityDB)
.WaitFor(securityDB)
.WithReference(rabbitmq)
.WithReference(serviceBus);
+//Hub
+var hub = builder.AddProject("ubiklink-commander")
+ .WithEnvironment("AuthRegister__HubSignSecureKey", hubSignSecureKey)
+ .WithReference(securityDB)
+ .WaitFor(securityDB)
+ .WithReference(rabbitmq)
+ .WithReference(serviceBus);
+
//Proxy
var proxy = builder.AddProject("ubiklink-proxy")
.WithEnvironment("Proxy__Token",securitytoken)
@@ -76,8 +86,10 @@
.WithReference(cache)
.WithReference(serviceBus)
.WithReference(rabbitmq)
+ .WithReference(hub)
.WaitFor(cache)
.WaitFor(securityApi)
+ .WaitFor(hub)
.WaitFor(keycloak);
//.WithReference(rabbitmq)
@@ -105,6 +117,7 @@
.WithEnvironment("Messaging__RabbitUser", rabbitUser)
.WithEnvironment("Messaging__RabbitPassword", rabbitPassword);
+
//Add npm sevltekit project (not work with fnm.... because of path)
//builder.AddNpmApp("svelte-ui", "../svelte-link-ui","dev")
// .WithEnvironment("BROWSER", "none")
diff --git a/UbikLink.AppHost/UbikLink.AppHost.csproj b/UbikLink.AppHost/UbikLink.AppHost.csproj
index e6e190f..6fe31a7 100644
--- a/UbikLink.AppHost/UbikLink.AppHost.csproj
+++ b/UbikLink.AppHost/UbikLink.AppHost.csproj
@@ -23,6 +23,7 @@
+
diff --git a/UbikLink.AppHost/appsettings.json b/UbikLink.AppHost/appsettings.json
index e00968f..6191af1 100644
--- a/UbikLink.AppHost/appsettings.json
+++ b/UbikLink.AppHost/appsettings.json
@@ -34,5 +34,6 @@
"Parameters:keycloak-password": "admin",
"Parameters:auth-token-store-key": "Ye6Y36ocA4SaGqYzd0HgmqMhVaM2jlkE",
"Parameters:auth-register-authorization-key": "Ye6Y36oddddcA4SaGqYzd0HgmqMhVaM2jlkE",
- "Parameters:email-activation-enable": "false"
+ "Parameters:email-activation-enable": "false",
+ "Parameters:hub-signtoken-key": "0YksXysNolWIH4K0dRzhZm/pv/q+TDVUujmCIsm/nlI="
}
diff --git a/UbikLink.Commander/Program.cs b/UbikLink.Commander/Program.cs
new file mode 100644
index 0000000..eeea6ce
--- /dev/null
+++ b/UbikLink.Commander/Program.cs
@@ -0,0 +1,89 @@
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Security.Cryptography;
+using UbikLink.Commander.Test;
+using UbikLink.Common.Api;
+using UbikLink.Common.Auth;
+
+//var SecurityKey = new SymmetricSecurityKey(RandomNumberGenerator.GetBytes(32));
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.AddServiceDefaults();
+
+// Add services to the container.
+// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
+//builder.Services.AddOpenApi();
+builder.Services.AddSignalR();
+builder.Services.AddCors();
+
+var keys = new AuthRegisterAuthKey();
+builder.Configuration.GetSection(AuthRegisterAuthKey.Position).Bind(keys);
+var SecurityKey = new SymmetricSecurityKey(Convert.FromBase64String(keys.HubSignSecureKey));
+
+
+builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddJwtBearer(options =>
+ {
+ options.TokenValidationParameters =
+ new TokenValidationParameters
+ {
+ LifetimeValidator = (before, expires, token, parameters) => expires > DateTime.UtcNow,
+ ValidateAudience = false,
+ ValidateIssuer = false,
+ ValidateActor = false,
+ ValidateLifetime = true,
+ IssuerSigningKey = SecurityKey
+ };
+
+ options.Events = new JwtBearerEvents
+ {
+ OnMessageReceived = context =>
+ {
+ var accessToken = context.Request.Query["access_token"];
+
+ if (!string.IsNullOrEmpty(accessToken))
+ {
+ context.Token = context.Request.Query["access_token"];
+ }
+ return Task.CompletedTask;
+ }
+ };
+ });
+
+builder.Services.AddAuthorizationBuilder()
+ .AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
+ {
+ policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
+ policy.RequireClaim(ClaimTypes.NameIdentifier);
+ policy.RequireClaim(ClaimTypes.UserData);
+ });
+
+
+var app = builder.Build();
+
+app.MapDefaultEndpoints();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ //app.MapOpenApi();
+}
+
+app.UseAuthentication();
+app.UseAuthorization();
+
+app.UseCors(x => x
+ .AllowAnyMethod()
+ .AllowAnyHeader()
+ .SetIsOriginAllowed(origin => true)
+ .AllowCredentials());
+
+app.MapHub("/chat");
+
+//app.UseHttpsRedirection();
+
+
+app.Run();
diff --git a/UbikLink.Commander/Properties/launchSettings.json b/UbikLink.Commander/Properties/launchSettings.json
new file mode 100644
index 0000000..77948ff
--- /dev/null
+++ b/UbikLink.Commander/Properties/launchSettings.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5122",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "https://localhost:7036;http://localhost:5122",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/UbikLink.Commander/Test/ChatHub.cs b/UbikLink.Commander/Test/ChatHub.cs
new file mode 100644
index 0000000..712f3c8
--- /dev/null
+++ b/UbikLink.Commander/Test/ChatHub.cs
@@ -0,0 +1,40 @@
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.SignalR;
+using System.Security.Claims;
+
+namespace UbikLink.Commander.Test
+{
+ [Authorize]
+ public class ChatHub : Hub
+ {
+ public override Task OnConnectedAsync()
+ {
+ var userId = Context.User?.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
+ var tenantId = Context.User?.Claims.FirstOrDefault(c => c.Type == ClaimTypes.UserData)?.Value;
+
+ if (userId == null || tenantId == null)
+ {
+ Context.Abort();
+ return Task.CompletedTask;
+ }
+
+ return base.OnConnectedAsync();
+ }
+
+ public override Task OnDisconnectedAsync(Exception? exception)
+ {
+ return base.OnDisconnectedAsync(exception);
+ }
+
+ public async Task SendMessage(string user, string message)
+ {
+ await Clients.All.ReceiveMessage(user,message);
+ }
+ }
+
+ public interface IChatClient
+ {
+ Task ReceiveMessage(string user, string message);
+ }
+}
diff --git a/UbikLink.Commander/UbikLink.Commander.csproj b/UbikLink.Commander/UbikLink.Commander.csproj
new file mode 100644
index 0000000..7f46f65
--- /dev/null
+++ b/UbikLink.Commander/UbikLink.Commander.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UbikLink.Commander/UbikLink.Commander.http b/UbikLink.Commander/UbikLink.Commander.http
new file mode 100644
index 0000000..2046073
--- /dev/null
+++ b/UbikLink.Commander/UbikLink.Commander.http
@@ -0,0 +1,6 @@
+@UbikLink.Commander_HostAddress = http://localhost:5122
+
+GET {{UbikLink.Commander_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/UbikLink.Commander/appsettings.Development.json b/UbikLink.Commander/appsettings.Development.json
new file mode 100644
index 0000000..ff66ba6
--- /dev/null
+++ b/UbikLink.Commander/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/UbikLink.Commander/appsettings.json b/UbikLink.Commander/appsettings.json
new file mode 100644
index 0000000..4d56694
--- /dev/null
+++ b/UbikLink.Commander/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/UbikLink.Common/Api/AuthRegisterAuthKey.cs b/UbikLink.Common/Api/AuthRegisterAuthKey.cs
index da92945..a217e39 100644
--- a/UbikLink.Common/Api/AuthRegisterAuthKey.cs
+++ b/UbikLink.Common/Api/AuthRegisterAuthKey.cs
@@ -11,6 +11,7 @@ public class AuthRegisterAuthKey
{
public const string Position = "AuthRegister";
public string Key { get; set; } = string.Empty;
+ public string HubSignSecureKey { get; set; } = string.Empty;
public bool EmailActivationActivated { get; set; } = false;
}
}
diff --git a/UbikLink.Proxy/Program.cs b/UbikLink.Proxy/Program.cs
index 1ce7daf..9f07235 100644
--- a/UbikLink.Proxy/Program.cs
+++ b/UbikLink.Proxy/Program.cs
@@ -118,25 +118,25 @@
})
.AddPolicy("CanReadTenant", policy =>
{
- policy.Requirements.Add(new UserTenantRolesOrAuthorizationsRequirement(new[] { "tenant:read" }, PermissionMode.Authorization, true));
+ policy.Requirements.Add(new UserTenantRolesOrAuthorizationsRequirement(["tenant:read"], PermissionMode.Authorization, true));
policy.RequireAuthenticatedUser();
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
})
.AddPolicy("CanReadTenantAndReadUser", policy =>
{
- policy.Requirements.Add(new UserTenantRolesOrAuthorizationsRequirement(new[] { "tenant:read", "user:read" }, PermissionMode.Authorization, true));
+ policy.Requirements.Add(new UserTenantRolesOrAuthorizationsRequirement(["tenant:read", "user:read"], PermissionMode.Authorization, true));
policy.RequireAuthenticatedUser();
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
})
.AddPolicy("CanReadTenantAndWriteUserRole", policy =>
{
- policy.Requirements.Add(new UserTenantRolesOrAuthorizationsRequirement(new[] { "tenant:read", "user:read", "tenant-user-role:write" }, PermissionMode.Authorization, true));
+ policy.Requirements.Add(new UserTenantRolesOrAuthorizationsRequirement(["tenant:read", "user:read", "tenant-user-role:write"], PermissionMode.Authorization, true));
policy.RequireAuthenticatedUser();
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
})
.AddPolicy("CanReadTenantAndReadTenantRoles", policy =>
{
- policy.Requirements.Add(new UserTenantRolesOrAuthorizationsRequirement(new[] { "tenant:read", "tenant-role:read" }, PermissionMode.Authorization, true));
+ policy.Requirements.Add(new UserTenantRolesOrAuthorizationsRequirement(["tenant:read", "tenant-role:read"], PermissionMode.Authorization, true));
policy.RequireAuthenticatedUser();
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
});
diff --git a/UbikLink.Proxy/appsettings.json b/UbikLink.Proxy/appsettings.json
index eec767e..744daaa 100644
--- a/UbikLink.Proxy/appsettings.json
+++ b/UbikLink.Proxy/appsettings.json
@@ -85,6 +85,22 @@
"Methods": [ "POST" ]
},
"Transforms": [ { "PathPattern": "/api/{apiversion}/users/register" } ]
+ },
+ "route_security_user_get_hub_token": {
+ "ClusterId": "ubiklink-security-api",
+ "AuthorizationPolicy": "IsUser",
+ "Match": {
+ "Path": "/security/api/{apiversion}/hubtoken",
+ "Methods": [ "GET" ]
+ },
+ "Transforms": [ { "PathPattern": "/api/{apiversion}/hubtoken" } ]
+ },
+ "route_commander_chathub": {
+ "ClusterId": "ubiklink-commander",
+ "Match": {
+ "Path": "/chathub"
+ },
+ "Transforms": [ { "PathPattern": "/chat" } ]
}
},
"Clusters": {
@@ -94,6 +110,13 @@
"Address": "http://ubiklink-security-api"
}
}
+ },
+ "ubiklink-commander": {
+ "Destinations": {
+ "destination1": {
+ "Address": "http://ubiklink-commander"
+ }
+ }
}
}
}
diff --git a/UbikLink.Security.Api/Features/Users/Errors/GetHubTokenError.cs b/UbikLink.Security.Api/Features/Users/Errors/GetHubTokenError.cs
new file mode 100644
index 0000000..9e29261
--- /dev/null
+++ b/UbikLink.Security.Api/Features/Users/Errors/GetHubTokenError.cs
@@ -0,0 +1,26 @@
+using UbikLink.Common.Errors;
+
+namespace UbikLink.Security.Api.Features.Users.Errors
+{
+ public record GetHubTokenError : IFeatureError
+ {
+ public FeatureErrorType ErrorType { get; init; }
+ public CustomError[] CustomErrors { get; init; }
+ public string Details => CustomErrors[0].ErrorFriendlyMessage;
+
+ public GetHubTokenError()
+ {
+
+ ErrorType = FeatureErrorType.BadParams;
+ CustomErrors = [ new()
+ {
+ ErrorCode = $"CANNOT_GET_HUB_TOKEN",
+ ErrorFriendlyMessage = $"The connected user request is not valid to receive a hub token.",
+ FieldsValuesInError = new Dictionary
+ {
+ { "userId", "connected user" },
+ }
+ }];
+ }
+ }
+}
diff --git a/UbikLink.Security.Api/Features/Users/Extensions/UserFeaturesRegistration.cs b/UbikLink.Security.Api/Features/Users/Extensions/UserFeaturesRegistration.cs
index d8ee736..9d61ac8 100644
--- a/UbikLink.Security.Api/Features/Users/Extensions/UserFeaturesRegistration.cs
+++ b/UbikLink.Security.Api/Features/Users/Extensions/UserFeaturesRegistration.cs
@@ -1,6 +1,7 @@
using UbikLink.Security.Api.Features.Users.Commands.OnboardMeSimple;
using UbikLink.Security.Api.Features.Users.Commands.RegisterUser;
using UbikLink.Security.Api.Features.Users.Commands.UpdateUserSettingsMe;
+using UbikLink.Security.Api.Features.Users.Queries.GetMyHubToken;
using UbikLink.Security.Api.Features.Users.Queries.GetUserForProxy;
using UbikLink.Security.Api.Features.Users.Queries.GetUserMe;
using UbikLink.Security.Api.Features.Users.Services;
@@ -24,6 +25,8 @@ public static void AddUserFeatures(this IServiceCollection services)
services.AddScoped();
services.AddScoped();
+
+ services.AddScoped();
}
}
}
diff --git a/UbikLink.Security.Api/Features/Users/Queries/GetMyHubToken/GetMyHubTokenEndpoint.cs b/UbikLink.Security.Api/Features/Users/Queries/GetMyHubToken/GetMyHubTokenEndpoint.cs
new file mode 100644
index 0000000..b44d69d
--- /dev/null
+++ b/UbikLink.Security.Api/Features/Users/Queries/GetMyHubToken/GetMyHubTokenEndpoint.cs
@@ -0,0 +1,30 @@
+using Microsoft.AspNetCore.Http.HttpResults;
+using Microsoft.AspNetCore.Mvc;
+using UbikLink.Common.Errors;
+using UbikLink.Common.Http;
+using UbikLink.Security.Api.Features.Users.Queries.GetUserMe;
+using UbikLink.Security.Contracts.Users.Results;
+
+namespace UbikLink.Security.Api.Features.Users.Queries.GetMyHubToken
+{
+ public class GetMyHubTokenEndpoint : IEndpoint
+ {
+ public void MapEndpoint(IEndpointRouteBuilder app)
+ {
+ app.MapGet("hubtoken",
+ async Task, ProblemHttpResult>> (
+ [FromServices] GetMyHubTokenHandler handler) =>
+ {
+ var result = await handler.Handle();
+
+ return result.Match, ProblemHttpResult>>(
+ ok => TypedResults.Ok(ok),
+ err => CustomTypedResults.Problem(err));
+ })
+ .WithSummary("Get my token to use real time functions")
+ .WithDescription("This endpoint get a token to etablish a real time connection.")
+ .WithTags("Realtime")
+ .ProducesProblem(400);
+ }
+ }
+}
diff --git a/UbikLink.Security.Api/Features/Users/Queries/GetMyHubToken/GetMyHubTokenHandler.cs b/UbikLink.Security.Api/Features/Users/Queries/GetMyHubToken/GetMyHubTokenHandler.cs
new file mode 100644
index 0000000..4f8bf71
--- /dev/null
+++ b/UbikLink.Security.Api/Features/Users/Queries/GetMyHubToken/GetMyHubTokenHandler.cs
@@ -0,0 +1,48 @@
+using LanguageExt;
+using UbikLink.Common.Api;
+using UbikLink.Common.Errors;
+using UbikLink.Security.Api.Features.Users.Services.Poco;
+using UbikLink.Security.Api.Features.Users.Services;
+using Microsoft.Extensions.Options;
+using UbikLink.Security.Api.Features.Users.Errors;
+using System.Security.Claims;
+using Microsoft.IdentityModel.Tokens;
+using System.Text;
+using System.IdentityModel.Tokens.Jwt;
+
+namespace UbikLink.Security.Api.Features.Users.Queries.GetMyHubToken
+{
+ public class GetMyHubTokenHandler(UserQueryService query, ICurrentUser currentUser, IOptions keys)
+ {
+ private readonly UserQueryService _query = query;
+ private readonly ICurrentUser _currentUser = currentUser;
+ private readonly AuthRegisterAuthKey _keys = keys.Value;
+
+ public async Task> Handle()
+ {
+ var user = await _query.GetUserById(_currentUser.Id);
+
+ var connected = user.Match(user =>
+ {
+ if (user.SelectedTenantId == null)
+ return null;
+
+ var claims = new[] { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
+ new Claim(ClaimTypes.UserData, user.SelectedTenantId.ToString()!)};
+
+ var keyBytes = Convert.FromBase64String(_keys.HubSignSecureKey);
+ var key = new SymmetricSecurityKey(keyBytes);
+ var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
+ var token = new JwtSecurityToken("UbikLinkHub", "UbikLink", claims, expires: DateTime.UtcNow.AddSeconds(30), signingCredentials: credentials);
+
+ var tokenHandler = new JwtSecurityTokenHandler();
+ return tokenHandler.WriteToken(token);
+
+ }, err => null);
+
+ return connected == null ?
+ new GetHubTokenError()
+ : connected;
+ }
+ }
+}
diff --git a/best-schema.png b/best-schema.png
index 5125791..6493634 100644
Binary files a/best-schema.png and b/best-schema.png differ
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..cce0ae7
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "yarp-security-api-and-ui",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}
diff --git a/svelte-link-ui/.github/copilot-instructions.md b/svelte-link-ui/.github/copilot-instructions.md
new file mode 100644
index 0000000..4cbd599
--- /dev/null
+++ b/svelte-link-ui/.github/copilot-instructions.md
@@ -0,0 +1,925 @@
+I'm using svelte 5 instead of svelte 4 here is an overview of the changes.
+
+#### Overview
+
+Svelte 5 introduces runes, a set of advanced primitives for controlling reactivity. The runes replace certain non-runes features and provide more explicit control over state and effects.
+
+#### $state
+
+- **Purpose:** Declare reactive state.
+- **Usage:**
+
+```javascript
+
+```
+
+- **Replaces:** Top-level `let` declarations in non-runes mode.
+- **Class Fields:**
+
+```javascript
+class Todo {
+ done = $state(false);
+ text = $state();
+ constructor(text) {
+ this.text = text;
+ }
+}
+```
+
+- **Deep Reactivity:** Only plain objects and arrays become deeply reactive.
+
+#### $state.raw
+
+- **Purpose:** Declare state that cannot be mutated, only reassigned.
+- **Usage:**
+
+```javascript
+
+```
+
+- **Performance:** Improves with large arrays and objects.
+
+#### $state.snapshot
+
+- **Purpose:** Take a static snapshot of $state.
+- **Usage:**
+
+```javascript
+
+```
+
+#### $derived
+
+- **Purpose:** Declare derived state.
+- **Usage:**
+
+```javascript
+
+```
+
+- **Replaces:** Reactive variables computed using `$:` in non-runes mode.
+
+#### $derived.by
+
+- **Purpose:** Create complex derivations with a function.
+- **Usage:**
+
+```javascript
+
+```
+
+#### $effect
+
+- **Purpose:** Run side-effects when values change.
+- **Usage:**
+
+```javascript
+
+```
+
+- **Replacements:** $effect replaces a substantial part of `$: {}` blocks triggering side-effects.
+
+#### $effect.pre
+
+- **Purpose:** Run code before the DOM updates.
+- **Usage:**
+
+```javascript
+
+```
+
+- **Replaces:** beforeUpdate.
+
+#### $effect.tracking
+
+- **Purpose:** Check if code is running inside a tracking context.
+- **Usage:**
+
+```javascript
+
+```
+
+#### $props
+
+- **Purpose:** Declare component props.
+- **Usage:**
+
+```javascript
+
+```
+
+- **Replaces:** export let syntax for declaring props.
+
+#### $bindable
+
+- **Purpose:** Declare bindable props.
+- **Usage:**
+
+```javascript
+
+```
+
+#### $inspect
+
+- **Purpose:** Equivalent to `console.log` but re-runs when its argument changes.
+- **Usage:**
+
+```javascript
+
+```
+
+#### $host
+
+- **Purpose:** Retrieve the this reference of the custom element.
+- **Usage:**
+
+```javascript
+
+```
+
+- **Note:** Only available inside custom element components on the client-side.
+
+#### Overview of snippets in svelte 5
+
+Snippets, along with render tags, help create reusable chunks of markup inside your components, reducing duplication and enhancing maintainability.
+
+#### Snippets Usage
+
+- **Definition:** Use the `#snippet` syntax to define reusable markup sections.
+- **Basic Example:**
+
+```javascript
+{#snippet figure(image)}
+
+
+ {image.caption}
+
+{/snippet}
+```
+
+- **Invocation:** Render predefined snippets with `@render`:
+
+```javascript
+{@render figure(image)}
+```
+
+- **Destructuring Parameters:** Parameters can be destructured for concise usage:
+
+```javascript
+{#snippet figure({ src, caption, width, height })}
+
+
+ {caption}
+
+{/snippet}
+```
+
+#### Snippet Scope
+
+- **Scope Rules:** Snippets have lexical scoping rules; they are visible to everything in the same lexical scope:
+
+```javascript
+
+ {#snippet x()}
+ {#snippet y()}...{/snippet}
+
+
+ {@render y()}
+ {/snippet}
+
+
+ {@render y()}
+
+
+
+{@render x()}
+```
+
+- **Recursive References:** Snippets can self-reference or reference other snippets:
+
+```javascript
+{#snippet blastoff()}
+ 🚀
+{/snippet}
+
+{#snippet countdown(n)}
+ {#if n > 0}
+ {n}...
+ {@render countdown(n - 1)}
+ {:else}
+ {@render blastoff()}
+ {/if}
+{/snippet}
+
+{@render countdown(10)}
+```
+
+#### Passing Snippets to Components
+
+- **Direct Passing as Props:**
+
+```javascript
+
+
+{#snippet header()}
+ fruit
+ qty
+ price
+ total
+{/snippet}
+
+{#snippet row(fruit)}
+ {fruit.name}
+ {fruit.qty}
+ {fruit.price}
+ {fruit.qty * fruit.price}
+{/snippet}
+
+
+```
+
+- **Implicit Binding:**
+
+```html
+
+ {#snippet header()}
+ fruit
+ qty
+ price
+ total
+ {/snippet} {#snippet row(fruit)}
+ {fruit.name}
+ {fruit.qty}
+ {fruit.price}
+ {fruit.qty * fruit.price}
+ {/snippet}
+
+```
+
+- **Children Snippet:** Non-snippet content defaults to the `children` snippet:
+
+```html
+
+ fruit
+ qty
+ price
+ total
+
+
+
+
+
+
+
+
+ {@render children()}
+
+
+
+
+```
+
+- **Avoid Conflicts:** Do not use a prop named `children` if also providing content inside the component.
+
+#### Typing Snippets
+
+- **TypeScript Integration:**
+
+```typescript
+
+```
+
+- **Generics for Improved Typing:**
+
+```typescript
+
+```
+
+#### Creating Snippets Programmatically
+
+- **Advanced Use:** Create snippets programmatically using `createRawSnippet` where necessary.
+
+#### Snippets and Slots
+
+- **Mixing with Slots:** Slots are deprecated but still work. Snippets provide more flexibility and power.
+- **Custom Elements:** Continue using ` ` for custom elements as usual.
+
+Sure! Here are the succinct instructions for handling Event Handlers in Svelte 5, tailored for the AI-integrated code editor to help it understand and utilize these features effectively.
+
+---
+
+### Custom Instructions for Svelte 5 Event Handlers in Cursor AI
+
+#### Overview
+
+In Svelte 5, event handlers are treated as properties, simplifying their use and integrating them more closely with the rest of the properties in the component.
+
+#### Basic Event Handlers
+
+- **Declaration:** Use properties to attach event handlers.
+
+```javascript
+
+
+ count++}>
+ clicks: {count}
+
+```
+
+- **Shorthand Syntax:**
+
+```javascript
+
+
+
+ clicks: {count}
+
+```
+
+- **Deprecation:** The traditional `on:` directive is deprecated.
+
+#### Component Events
+
+- **Replacing createEventDispatcher:** Components should accept callback props instead of using `createEventDispatcher`.
+
+```javascript
+
+
+ { size += power; if (size > 75) burst = true; }}
+ deflate={(power) => { if (size > 0) size -= power; }}
+/>
+
+{#if burst}
+ new balloon
+ 💥
+{:else}
+ 🎈
+{/if}
+```
+
+#### Bubbling Events
+
+- **Accept Callback Props:**
+
+```javascript
+
+
+
+ {@render children()}
+
+```
+
+- **Spreading Props:**
+
+```javascript
+
+
+
+ {@render children()}
+
+```
+
+#### Event Modifiers
+
+- **Avoiding Modifiers:** Modifiers like `|once`, `|preventDefault`, etc., are not supported. Use wrapper functions instead.
+- **Example Wrapper Functions:**
+
+```javascript
+
+
+...
+```
+
+- **Special Modifiers:** For `capture`:
+
+```javascript
+...
+```
+
+#### Multiple Event Handlers
+
+- **Combining Handlers:** Instead of using multiple handlers, combine them into one.
+
+```javascript
+ {
+ handlerOne(e);
+ handlerTwo(e);
+ }}
+>
+ ...
+
+```
+
+---
+
+### examples old vs new
+
+#### Counter Example
+
+- **Svelte 4 vs. Svelte 5:**
+
+ - **Before:**
+
+ ```html
+
+
+ count++}> {count} / {double}
+ ```
+
+ - **After:**
+
+ ```html
+
+
+ count++}> {count} / {double}
+ ```
+
+#### Tracking Dependencies
+
+- **Svelte 4 vs. Svelte 5:**
+
+ - **Before:**
+
+ ```html
+
+
+ a++}>a++
+ b++}>b++
+ {a} + {b} = {sum}
+ ```
+
+ - **After:**
+
+ ```html
+
+
+ a++}>a++
+ b++}>b++
+ {a} + {b} = {sum}
+ ```
+
+#### Untracking Dependencies
+
+- **Svelte 4 vs. Svelte 5:**
+
+ - **Before:**
+
+ ```html
+
+
+ a++}>a++
+ b++}>b++
+ {a} + {b} = {sum}
+ ```
+
+ - **After:**
+
+ ```html
+
+
+ a++}>a++
+ b++}>b++
+ {a} + {b} = {sum}
+ ```
+
+#### Simple Component Props
+
+- **Svelte 5:**
+
+ ```html
+
+
+ {count}
+ ```
+
+#### Advanced Component Props
+
+- **Svelte 5:**
+
+ ```html
+
+
+
+ {JSON.stringify(others)}
+
+ ```
+
+#### Autoscroll Example
+
+- **Svelte 4 vs. Svelte 5:**
+
+ - **Before:**
+
+ ```html
+
+
+
+
+ {#each messages as message}
+
{message}
+ {/each}
+
+
+
+
Toggle dark mode
+
+ ```
+
+ - **After:**
+
+ ```html
+
+
+
+
+ {#each messages as message}
+
{message}
+ {/each}
+
+
+
+
Toggle dark mode
+
+ ```
+
+#### Forwarding Events
+
+- **Svelte 5:**
+
+ ```html
+
+
+ a button
+ ```
+
+#### Passing UI Content to a Component
+
+- **Passing content using snippets:**
+
+ ```html
+
+
+
+ {#snippet children(prop)} click {prop} {/snippet}
+
+
+
+
+ {@render children("some value")}
+ ```
+
+I'm also using sveltekit 2 which also has some changes I'd like you to keep in mind
+
+### Redirect and Error Handling
+
+In SvelteKit 2, it is no longer necessary to throw the results of `error(...)` and `redirect(...)`. Simply calling them is sufficient.
+
+**SvelteKit 1:**
+
+```javascript
+import { error } from '@sveltejs/kit';
+
+function load() {
+ throw error(500, 'something went wrong');
+}
+```
+
+**SvelteKit 2:**
+
+```javascript
+import { error } from '@sveltejs/kit';
+
+function load() {
+ error(500, 'something went wrong');
+}
+```
+
+**Distinguish Errors:**
+Use `isHttpError` and `isRedirect` to differentiate known errors from unexpected ones.
+
+```javascript
+import { isHttpError, isRedirect } from '@sveltejs/kit';
+
+try {
+ // some code
+} catch (err) {
+ if (isHttpError(err) || isRedirect(err)) {
+ // handle error
+ }
+}
+```
+
+### Cookie Path Requirement
+
+Cookies now require a specified path when set, deleted, or serialized.
+
+**SvelteKit 1:**
+
+```javascript
+export function load({ cookies }) {
+ cookies.set(name, value);
+ return { response };
+}
+```
+
+**SvelteKit 2:**
+
+```javascript
+export function load({ cookies }) {
+ cookies.set(name, value, { path: '/' });
+ return { response };
+}
+```
+
+### Top-Level Promise Handling
+
+Promises in `load` functions are no longer awaited automatically.
+
+**Single Promise:**
+
+**SvelteKit 1:**
+
+```javascript
+export function load({ fetch }) {
+ return {
+ response: fetch(...).then(r => r.json())
+ };
+}
+```
+
+**SvelteKit 2:**
+
+```javascript
+export async function load({ fetch }) {
+ const response = await fetch(...).then(r => r.json());
+ return { response };
+}
+```
+
+**Multiple Promises:**
+
+**SvelteKit 1:**
+
+```javascript
+export function load({ fetch }) {
+ return {
+ a: fetch(...).then(r => r.json()),
+ b: fetch(...).then(r => r.json())
+ };
+}
+```
+
+**SvelteKit 2:**
+
+```javascript
+export async function load({ fetch }) {
+ const [a, b] = await Promise.all([
+ fetch(...).then(r => r.json()),
+ fetch(...).then(r => r.json())
+ ]);
+ return { a, b };
+}
+```
+
+### `goto` Changes
+
+`goto(...)` no longer accepts external URLs. Use `window.location.href = url` for external navigation.
+
+### Relative Paths Default
+
+Paths are now relative by default, ensuring portability across different environments. The `paths.relative` config option manages this behavior.
+
+### Deprecated Settings and Functions
+
+- **Server Fetches** are no longer trackable.
+- **`preloadCode` Arguments:** Must be prefixed with the base path.
+- **`resolvePath` Replacement:** Use `resolveRoute` instead.
+
+```javascript
+import { resolveRoute } from '$app/paths';
+
+const path = resolveRoute('/blog/[slug]', { slug: 'hello' });
+```
+
+### Improved Error Handling
+
+Errors trigger the `handleError` hook with `status` and `message` properties for better discernment.
+
+### Dynamic Environment Variables
+
+Dynamic environment variables cannot be used during prerendering. Use static modules instead.
+
+### `use:enhance` Callback Changes
+
+The properties `form` and `data` have been removed from `use:enhance` callbacks, replaced by `formElement` and `formData`.
+
+### Forms with File Inputs
+
+Forms containing ` ` must use `enctype="multipart/form-data"`.
+
+With these adjusted guidelines, your AI can now generate SvelteKit 2 code accurately while considering the migration changes.
diff --git a/svelte-link-ui/package-lock.json b/svelte-link-ui/package-lock.json
index 668fd59..11b362d 100644
--- a/svelte-link-ui/package-lock.json
+++ b/svelte-link-ui/package-lock.json
@@ -8,6 +8,7 @@
"name": "svelte-link-ui",
"version": "0.0.1",
"dependencies": {
+ "@microsoft/signalr": "^8.0.7",
"@pilcrowjs/object-parser": "^0.0.4",
"@tailwindcss/vite": "^4.0.7",
"@types/crypto-js": "^4.2.2",
@@ -771,6 +772,19 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@microsoft/signalr": {
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.7.tgz",
+ "integrity": "sha512-PHcdMv8v5hJlBkRHAuKG5trGViQEkPYee36LnJQx4xHOQ5LL4X0nEWIxOp5cCtZ7tu+30quz5V3k0b1YNuc6lw==",
+ "license": "MIT",
+ "dependencies": {
+ "abort-controller": "^3.0.0",
+ "eventsource": "^2.0.2",
+ "fetch-cookie": "^2.0.3",
+ "node-fetch": "^2.6.7",
+ "ws": "^7.4.5"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1673,6 +1687,18 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "license": "MIT",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
@@ -2322,6 +2348,24 @@
"node": ">=0.10.0"
}
},
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
+ "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -2398,6 +2442,16 @@
}
}
},
+ "node_modules/fetch-cookie": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz",
+ "integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==",
+ "license": "Unlicense",
+ "dependencies": {
+ "set-cookie-parser": "^2.4.8",
+ "tough-cookie": "^4.0.0"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -3149,6 +3203,26 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -3495,16 +3569,33 @@
}
}
},
+ "node_modules/psl": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+ "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/lupomontero"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "license": "MIT"
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -3570,6 +3661,12 @@
"node": ">=4"
}
},
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "license": "MIT"
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -3698,7 +3795,6 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/shebang-command": {
@@ -4069,6 +4165,27 @@
"node": ">=6"
}
},
+ "node_modules/tough-cookie": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
+ },
"node_modules/ts-api-utils": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz",
@@ -4144,6 +4261,15 @@
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"license": "MIT"
},
+ "node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -4154,6 +4280,16 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -4251,6 +4387,22 @@
}
}
},
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -4277,6 +4429,27 @@
"node": ">=0.10.0"
}
},
+ "node_modules/ws": {
+ "version": "7.5.10",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
+ "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/yaml": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
diff --git a/svelte-link-ui/package.json b/svelte-link-ui/package.json
index 879acde..6990b17 100644
--- a/svelte-link-ui/package.json
+++ b/svelte-link-ui/package.json
@@ -33,6 +33,7 @@
"vite": "^6.0.0"
},
"dependencies": {
+ "@microsoft/signalr": "^8.0.7",
"@pilcrowjs/object-parser": "^0.0.4",
"@tailwindcss/vite": "^4.0.7",
"@types/crypto-js": "^4.2.2",
diff --git a/svelte-link-ui/src/app.css b/svelte-link-ui/src/app.css
index 7c6df53..c3c6560 100644
--- a/svelte-link-ui/src/app.css
+++ b/svelte-link-ui/src/app.css
@@ -19,6 +19,11 @@
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
+ @font-face {
+ font-family: Geist;
+ font-display: swap;
+ src: url('/fonts/geist.woff2') format('woff2');
+ }
}
:root {
@@ -42,6 +47,14 @@
--input: 20 5.9% 90%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
+ --sidebar-background: 0 0% 98%;
+ --sidebar-foreground: 240 5.3% 26.1%;
+ --sidebar-primary: 240 5.9% 10%;
+ --sidebar-primary-foreground: 0 0% 98%;
+ --sidebar-accent: 240 4.8% 95.9%;
+ --sidebar-accent-foreground: 240 5.9% 10%;
+ --sidebar-border: 220 13% 91%;
+ --sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 20 14.3% 4.1%;
@@ -63,12 +76,20 @@
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--ring: 24 5.7% 82.9%;
+ --sidebar-background: 240 5.9% 10%;
+ --sidebar-foreground: 240 4.8% 95.9%;
+ --sidebar-primary: 224.3 76.3% 48%;
+ --sidebar-primary-foreground: 0 0% 100%;
+ --sidebar-accent: 240 3.7% 15.9%;
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
+ --sidebar-border: 240 3.7% 15.9%;
+ --sidebar-ring: 217.2 91.2% 59.8%;
}
@theme inline {
/* Fonts */
--font-sans:
- 'Inter Variable', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
+ Geist, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
--font-mono:
'Source Code Pro Variable', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
@@ -119,19 +140,24 @@
/* Keyframes */
@keyframes accordion-down {
- from: {
- height: 0;
+ from {
+ transform: scaleY(0);
+ transform-origin: top;
}
- to: {
- height: var(--bits-accordion-content-height);
+ to {
+ transform: scaleY(1);
+ transform-origin: top;
}
}
+
@keyframes accordion-up {
- from: {
- height: var(--bits-accordion-content-height);
+ from {
+ transform: scaleY(1);
+ transform-origin: top;
}
- to: {
- height: 0;
+ to {
+ transform: scaleY(0);
+ transform-origin: top;
}
}
@keyframes caret-blink {
@@ -154,4 +180,13 @@
body {
@apply bg-background text-foreground;
}
+ p {
+ @apply leading-7 [&:not(:first-child)]:mt-6;
+ }
+ h1 {
+ @apply scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl;
+ }
+ h2 {
+ @apply scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0;
+ }
}
diff --git a/svelte-link-ui/src/hooks.server.ts b/svelte-link-ui/src/hooks.server.ts
index a2bd464..98b397d 100644
--- a/svelte-link-ui/src/hooks.server.ts
+++ b/svelte-link-ui/src/hooks.server.ts
@@ -16,7 +16,7 @@ import {
removeAllUsersFromCache,
removeUserFromCache
} from './lib/server/user';
-import type { UserMeResult } from '$lib/types/user-types';
+import type { UserMeResult } from '$lib/shared-types/user-types';
const bucket = new TokenBucket(100, 1);
const LOGIN_URL = '/login/auth';
diff --git a/svelte-link-ui/src/lib/assets/img/logo2.svg b/svelte-link-ui/src/lib/assets/img/logo2.svg
new file mode 100644
index 0000000..ffa32b1
--- /dev/null
+++ b/svelte-link-ui/src/lib/assets/img/logo2.svg
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/svelte-link-ui/src/lib/components/communication/chat.svelte b/svelte-link-ui/src/lib/components/communication/chat.svelte
new file mode 100644
index 0000000..16ffaa3
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/communication/chat.svelte
@@ -0,0 +1,122 @@
+
+
+
+
Chat
+
+
+
+
+
{chat.connectionStatus}
+
+
+
+
+ {#if messages.length === 0}
+
No messages yet
+ {:else}
+ {#each messages as message}
+ {#if messageItem}
+ {@render messageItem(message)}
+ {:else}
+
+ {/if}
+ {/each}
+ {/if}
+
+
+
+
+
+
+
diff --git a/svelte-link-ui/src/lib/components/communication/signalr.svelte.ts b/svelte-link-ui/src/lib/components/communication/signalr.svelte.ts
new file mode 100644
index 0000000..cf8b193
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/communication/signalr.svelte.ts
@@ -0,0 +1,340 @@
+import {
+ HubConnection,
+ HubConnectionBuilder,
+ HubConnectionState,
+ LogLevel,
+ HttpTransportType
+} from '@microsoft/signalr';
+import { browser } from '$app/environment';
+
+//TODO: no log on prod.
+const enableLogging = true;
+/**
+ * Defines error types for the SignalR connection
+ */
+export type SignalRErrorType =
+ | 'CONNECTION_ERROR'
+ | 'DISCONNECTION_ERROR'
+ | 'INVOCATION_ERROR'
+ | 'HANDLER_ERROR'
+ | 'RECONNECTION_ERROR';
+
+/**
+ * Structured error object for SignalR operations
+ */
+export type SignalRError = {
+ type: SignalRErrorType;
+ message: string;
+ timestamp: Date;
+ details?: unknown;
+};
+
+/**
+ * Options for configuring a SignalR connection
+ */
+export type SignalRConnectionOptions = {
+ /** Hub URL to connect to */
+ hubUrl: string;
+ /** Optional authentication token callback method */
+ authTokenCallback: () => Promise;
+ /** Auto-reconnect intervals in milliseconds */
+ reconnectIntervals?: number[];
+ /** Whether to enable detailed logging */
+ /** Whether to auto-connect when the class is instantiated */
+ autoConnect?: boolean;
+ /** Whether to use WebSockets transport only */
+ useWebSocketsOnly?: boolean;
+ /** Whether to skip negotiation phase (useful for CORS issues) */
+ skipNegotiation?: boolean;
+ /** Optional callback for handling errors */
+ onError?: (error: SignalRError) => void;
+};
+
+/**
+ * SignalR connection state manager
+ */
+export class SignalRConnection {
+ // State properties using Svelte 5 $state rune
+ connection = $state(null);
+ connectionStatus = $state<
+ 'Connected' | 'Connecting' | 'Reconnecting' | 'Disconnected' | 'Failed'
+ >('Disconnected');
+
+ // Error handling state
+ errors = $state([]);
+ latestError = $state(null);
+
+ // Connection metrics
+ connectionAttempts = $state(0);
+ reconnectionAttempts = $state(0);
+ lastConnectedAt = $state(null);
+
+ // Derived state for easy UI checks
+ hasError = $derived(this.latestError !== null);
+ canReconnect = $derived(
+ this.connectionStatus === 'Disconnected' || this.connectionStatus === 'Failed'
+ );
+
+ // Configuration options
+ #options: SignalRConnectionOptions;
+ #maxErrorsStored = 8;
+ #handlers = $state(new Set());
+
+ constructor(options: SignalRConnectionOptions) {
+ this.#options = {
+ reconnectIntervals: [0, 2000, 10000, 30000],
+ autoConnect: false,
+ useWebSocketsOnly: true,
+ skipNegotiation: false,
+ ...options
+ };
+
+ if (browser && this.#options.autoConnect) {
+ this.connect();
+ }
+ }
+
+ /**
+ * Connect to the SignalR hub
+ */
+ async connect(): Promise {
+ if (!browser) return;
+
+ if (this.connection && this.connection.state !== HubConnectionState.Disconnected) {
+ return;
+ }
+
+ try {
+ this.connectionStatus = 'Connecting';
+ this.connectionAttempts++;
+
+ const builder = new HubConnectionBuilder().withUrl(this.#options.hubUrl, {
+ skipNegotiation: this.#options.skipNegotiation,
+ transport: this.#options.useWebSocketsOnly ? HttpTransportType.WebSockets : undefined,
+ accessTokenFactory: this.#options.authTokenCallback
+ });
+
+ // Add logging if enabled
+ if (enableLogging) {
+ builder.configureLogging(LogLevel.Information);
+ } else {
+ builder.configureLogging(LogLevel.None);
+ }
+
+ // Add reconnect logic
+ if (this.#options.reconnectIntervals && this.#options.reconnectIntervals.length > 0) {
+ builder.withAutomaticReconnect(this.#options.reconnectIntervals);
+ }
+
+ // Build the connection
+ this.connection = builder.build();
+ // Set up connection lifecycle event handlers
+ this.#setupConnectionEvents();
+
+ // Start the connection
+ await this.connection.start();
+
+ this.connectionStatus = 'Connected';
+ this.lastConnectedAt = new Date();
+
+ // Clear reconnection attempts on successful connection
+ this.reconnectionAttempts = 0;
+ } catch (err) {
+ this.connectionStatus = 'Failed';
+ this.#addError('CONNECTION_ERROR', 'Failed to establish connection', err);
+ throw err;
+ }
+ }
+
+ /**
+ * Disconnect from the SignalR hub
+ */
+ async disconnect(): Promise {
+ if (!this.connection) return;
+
+ try {
+ await this.connection.stop();
+ this.connectionStatus = 'Disconnected';
+ } catch (err) {
+ this.#addError('DISCONNECTION_ERROR', 'Error disconnecting from SignalR hub', err);
+ // No need to rethrow as disconnection errors are usually non-critical
+ }
+ }
+
+ /**
+ * Send a message to a specific method on the SignalR hub
+ * @template TArgs - Array of argument types for the method
+ * @param methodName - The name of the server method to call
+ * @param args - The arguments to pass to the method
+ */
+ async send(methodName: string, ...args: TArgs): Promise {
+ if (!this.connection || this.connection.state !== HubConnectionState.Connected) {
+ const error = new Error('Cannot send message: No active connection');
+ this.#addError(
+ 'INVOCATION_ERROR',
+ `Cannot invoke method '${methodName}': No active connection`,
+ { methodName, args }
+ );
+ throw error;
+ }
+
+ try {
+ await this.connection.invoke(methodName, ...args);
+ } catch (err) {
+ this.#addError('INVOCATION_ERROR', `Error invoking method '${methodName}'`, err);
+ throw err;
+ }
+ }
+
+ /**
+ * Listen for messages from a specific method on the SignalR hub
+ * @template TArgs - Array of argument types from the hub method
+ * @param methodName - The name of the client method to register
+ * @param callback - The callback to execute when the method is invoked
+ */
+ on(
+ methodName: string,
+ callback: (...args: TArgs) => void
+ ): void {
+ if (!this.connection) {
+ const error = new Error('Cannot register handlers: No connection created');
+ this.#addError(
+ 'HANDLER_ERROR',
+ `Cannot register handler for '${methodName}': No connection created`,
+ { methodName }
+ );
+ throw error;
+ }
+
+ try {
+ this.connection.on(methodName, callback);
+ this.#handlers.add(methodName);
+ } catch (err) {
+ this.#addError('HANDLER_ERROR', `Error registering handler for method '${methodName}'`, err);
+ throw err;
+ }
+ }
+
+ /**
+ * Remove a handler for a specific method
+ * @param methodName - The name of the client method to unregister
+ * @param callback - Optional specific callback to remove
+ */
+ off(
+ methodName: string,
+ callback?: (...args: TArgs) => void
+ ): void {
+ if (!this.connection) return;
+
+ try {
+ if (callback) {
+ this.connection.off(methodName, callback);
+ } else {
+ this.connection.off(methodName);
+ }
+
+ // Update the handlers tracking
+ if (!callback) {
+ this.#handlers.delete(methodName);
+ }
+ } catch (err) {
+ this.#addError('HANDLER_ERROR', `Error removing handler for method '${methodName}'`, err);
+ }
+ }
+
+ /**
+ * Attempts to reconnect if disconnected
+ */
+ async reconnect(): Promise {
+ if (
+ this.connectionStatus === 'Connected' ||
+ this.connectionStatus === 'Connecting' ||
+ this.connectionStatus === 'Reconnecting'
+ ) {
+ return;
+ }
+
+ this.reconnectionAttempts++;
+ try {
+ await this.connect();
+ } catch (err) {
+ this.#addError(
+ 'RECONNECTION_ERROR',
+ `Manual reconnection attempt ${this.reconnectionAttempts} failed`,
+ err
+ );
+ }
+ }
+
+ /**
+ * Clear error history
+ */
+ clearErrors(): void {
+ this.errors = [];
+ this.latestError = null;
+ }
+
+ /**
+ * Check if the connection is active
+ */
+ get isConnected(): boolean {
+ return this.connectionStatus === 'Connected';
+ }
+
+ /**
+ * Get a list of registered handlers
+ */
+ get registeredHandlers(): string[] {
+ return [...this.#handlers];
+ }
+
+ // Private methods
+ #setupConnectionEvents(): void {
+ if (!this.connection) return;
+
+ this.connection.onclose((error) => {
+ this.connectionStatus = 'Disconnected';
+ if (error) {
+ this.#addError('CONNECTION_ERROR', 'Connection closed with error', error);
+ }
+ });
+
+ this.connection.onreconnecting((error) => {
+ this.connectionStatus = 'Reconnecting';
+ this.reconnectionAttempts++;
+ if (error) {
+ this.#addError('RECONNECTION_ERROR', 'Attempting to reconnect', error);
+ }
+ });
+
+ this.connection.onreconnected((connectionId) => {
+ this.connectionStatus = 'Connected';
+ this.lastConnectedAt = new Date();
+ });
+ }
+
+ #addError(type: SignalRErrorType, message: string, details?: unknown): void {
+ const error: SignalRError = {
+ type,
+ message,
+ timestamp: new Date(),
+ details
+ };
+
+ // Update latest error
+ this.latestError = error;
+
+ // Add to errors array with limit
+ this.errors = [error, ...this.errors].slice(0, this.#maxErrorsStored);
+
+ // Call error handler if provided
+ if (this.#options.onError) {
+ try {
+ this.#options.onError(error);
+ } catch {
+ // Prevent errors in the error handler from causing issues
+ // We intentionally suppress these errors to avoid infinite loops
+ }
+ }
+ }
+}
diff --git a/svelte-link-ui/src/lib/components/communication/utils.ts b/svelte-link-ui/src/lib/components/communication/utils.ts
new file mode 100644
index 0000000..6f6bc98
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/communication/utils.ts
@@ -0,0 +1,15 @@
+export type hubTokenType = 'chat'
+
+export async function getHubToken(tokenType : hubTokenType): Promise {
+ try {
+ const response = await fetch('/app/hubtokens/chat');
+ if (response.ok) {
+ const result = await response.text();
+ return result;
+ } else {
+ return '';
+ }
+ } catch (error) {
+ return '';
+ }
+}
\ No newline at end of file
diff --git a/svelte-link-ui/src/lib/components/header-app.svelte b/svelte-link-ui/src/lib/components/header-app.svelte
new file mode 100644
index 0000000..5ff1d72
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/header-app.svelte
@@ -0,0 +1,20 @@
+
+
+
diff --git a/svelte-link-ui/src/lib/components/sidebar-app.svelte b/svelte-link-ui/src/lib/components/sidebar-app.svelte
new file mode 100644
index 0000000..2479960
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/sidebar-app.svelte
@@ -0,0 +1,43 @@
+
+
+
diff --git a/svelte-link-ui/src/lib/components/toggle-theme.svelte b/svelte-link-ui/src/lib/components/toggle-theme.svelte
index d3d3ae8..1ab44af 100644
--- a/svelte-link-ui/src/lib/components/toggle-theme.svelte
+++ b/svelte-link-ui/src/lib/components/toggle-theme.svelte
@@ -6,7 +6,7 @@
import { Button } from '$lib/components/ui/button/index.js';
-
+
diff --git a/svelte-link-ui/src/lib/components/ui/sheet/index.ts b/svelte-link-ui/src/lib/components/ui/sheet/index.ts
new file mode 100644
index 0000000..d315263
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sheet/index.ts
@@ -0,0 +1,37 @@
+import { Dialog as SheetPrimitive } from 'bits-ui';
+
+import Overlay from './sheet-overlay.svelte';
+import Content from './sheet-content.svelte';
+import Header from './sheet-header.svelte';
+import Footer from './sheet-footer.svelte';
+import Title from './sheet-title.svelte';
+import Description from './sheet-description.svelte';
+
+const Root = SheetPrimitive.Root;
+const Close = SheetPrimitive.Close;
+const Trigger = SheetPrimitive.Trigger;
+const Portal = SheetPrimitive.Portal;
+
+export {
+ Root,
+ Close,
+ Trigger,
+ Portal,
+ Overlay,
+ Content,
+ Header,
+ Footer,
+ Title,
+ Description,
+ //
+ Root as Sheet,
+ Close as SheetClose,
+ Trigger as SheetTrigger,
+ Portal as SheetPortal,
+ Overlay as SheetOverlay,
+ Content as SheetContent,
+ Header as SheetHeader,
+ Footer as SheetFooter,
+ Title as SheetTitle,
+ Description as SheetDescription
+};
diff --git a/svelte-link-ui/src/lib/components/ui/sheet/sheet-content.svelte b/svelte-link-ui/src/lib/components/ui/sheet/sheet-content.svelte
new file mode 100644
index 0000000..6dd24f2
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sheet/sheet-content.svelte
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+ {@render children?.()}
+
+
+ Close
+
+
+
diff --git a/svelte-link-ui/src/lib/components/ui/sheet/sheet-description.svelte b/svelte-link-ui/src/lib/components/ui/sheet/sheet-description.svelte
new file mode 100644
index 0000000..83574ad
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sheet/sheet-description.svelte
@@ -0,0 +1,16 @@
+
+
+
diff --git a/svelte-link-ui/src/lib/components/ui/sheet/sheet-footer.svelte b/svelte-link-ui/src/lib/components/ui/sheet/sheet-footer.svelte
new file mode 100644
index 0000000..6c0e68d
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sheet/sheet-footer.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sheet/sheet-header.svelte b/svelte-link-ui/src/lib/components/ui/sheet/sheet-header.svelte
new file mode 100644
index 0000000..691cbee
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sheet/sheet-header.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sheet/sheet-overlay.svelte b/svelte-link-ui/src/lib/components/ui/sheet/sheet-overlay.svelte
new file mode 100644
index 0000000..7ed6781
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sheet/sheet-overlay.svelte
@@ -0,0 +1,19 @@
+
+
+
diff --git a/svelte-link-ui/src/lib/components/ui/sheet/sheet-title.svelte b/svelte-link-ui/src/lib/components/ui/sheet/sheet-title.svelte
new file mode 100644
index 0000000..7696932
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sheet/sheet-title.svelte
@@ -0,0 +1,16 @@
+
+
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/constants.ts b/svelte-link-ui/src/lib/components/ui/sidebar/constants.ts
new file mode 100644
index 0000000..e0ee736
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/constants.ts
@@ -0,0 +1,6 @@
+export const SIDEBAR_COOKIE_NAME = 'sidebar:state';
+export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
+export const SIDEBAR_WIDTH = '14rem';
+export const SIDEBAR_WIDTH_MOBILE = '18rem';
+export const SIDEBAR_WIDTH_ICON = '3rem';
+export const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/context.svelte.ts b/svelte-link-ui/src/lib/components/ui/sidebar/context.svelte.ts
new file mode 100644
index 0000000..e86e909
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/context.svelte.ts
@@ -0,0 +1,79 @@
+import { IsMobile } from '$lib/hook/is-mobile.svelte.js';
+import { getContext, setContext } from 'svelte';
+import { SIDEBAR_KEYBOARD_SHORTCUT } from './constants.js';
+
+type Getter = () => T;
+
+export type SidebarStateProps = {
+ /**
+ * A getter function that returns the current open state of the sidebar.
+ * We use a getter function here to support `bind:open` on the `Sidebar.Provider`
+ * component.
+ */
+ open: Getter;
+
+ /**
+ * A function that sets the open state of the sidebar. To support `bind:open`, we need
+ * a source of truth for changing the open state to ensure it will be synced throughout
+ * the sub-components and any `bind:` references.
+ */
+ setOpen: (open: boolean) => void;
+};
+
+class SidebarState {
+ readonly props: SidebarStateProps;
+ open = $derived.by(() => this.props.open());
+ openMobile = $state(false);
+ setOpen: SidebarStateProps['setOpen'];
+ #isMobile: IsMobile;
+ state = $derived.by(() => (this.open ? 'expanded' : 'collapsed'));
+
+ constructor(props: SidebarStateProps) {
+ this.setOpen = props.setOpen;
+ this.#isMobile = new IsMobile();
+ this.props = props;
+ }
+
+ // Convenience getter for checking if the sidebar is mobile
+ // without this, we would need to use `sidebar.isMobile.current` everywhere
+ get isMobile() {
+ return this.#isMobile.current;
+ }
+
+ // Event handler to apply to the ``
+ handleShortcutKeydown = (e: KeyboardEvent) => {
+ if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) {
+ e.preventDefault();
+ this.toggle();
+ }
+ };
+
+ setOpenMobile = (value: boolean) => {
+ this.openMobile = value;
+ };
+
+ toggle = () => {
+ return this.#isMobile.current ? (this.openMobile = !this.openMobile) : this.setOpen(!this.open);
+ };
+}
+
+const SYMBOL_KEY = 'scn-sidebar';
+
+/**
+ * Instantiates a new `SidebarState` instance and sets it in the context.
+ *
+ * @param props The constructor props for the `SidebarState` class.
+ * @returns The `SidebarState` instance.
+ */
+export function setSidebar(props: SidebarStateProps): SidebarState {
+ return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props));
+}
+
+/**
+ * Retrieves the `SidebarState` instance from the context. This is a class instance,
+ * so you cannot destructure it.
+ * @returns The `SidebarState` instance.
+ */
+export function useSidebar(): SidebarState {
+ return getContext(Symbol.for(SYMBOL_KEY));
+}
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/index.ts b/svelte-link-ui/src/lib/components/ui/sidebar/index.ts
new file mode 100644
index 0000000..280e640
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/index.ts
@@ -0,0 +1,75 @@
+import { useSidebar } from './context.svelte.js';
+import Content from './sidebar-content.svelte';
+import Footer from './sidebar-footer.svelte';
+import GroupAction from './sidebar-group-action.svelte';
+import GroupContent from './sidebar-group-content.svelte';
+import GroupLabel from './sidebar-group-label.svelte';
+import Group from './sidebar-group.svelte';
+import Header from './sidebar-header.svelte';
+import Input from './sidebar-input.svelte';
+import Inset from './sidebar-inset.svelte';
+import MenuAction from './sidebar-menu-action.svelte';
+import MenuBadge from './sidebar-menu-badge.svelte';
+import MenuButton from './sidebar-menu-button.svelte';
+import MenuItem from './sidebar-menu-item.svelte';
+import MenuSkeleton from './sidebar-menu-skeleton.svelte';
+import MenuSubButton from './sidebar-menu-sub-button.svelte';
+import MenuSubItem from './sidebar-menu-sub-item.svelte';
+import MenuSub from './sidebar-menu-sub.svelte';
+import Menu from './sidebar-menu.svelte';
+import Provider from './sidebar-provider.svelte';
+import Rail from './sidebar-rail.svelte';
+import Separator from './sidebar-separator.svelte';
+import Trigger from './sidebar-trigger.svelte';
+import Root from './sidebar.svelte';
+
+export {
+ Content,
+ Footer,
+ Group,
+ GroupAction,
+ GroupContent,
+ GroupLabel,
+ Header,
+ Input,
+ Inset,
+ Menu,
+ MenuAction,
+ MenuBadge,
+ MenuButton,
+ MenuItem,
+ MenuSkeleton,
+ MenuSub,
+ MenuSubButton,
+ MenuSubItem,
+ Provider,
+ Rail,
+ Root,
+ Separator,
+ //
+ Root as Sidebar,
+ Content as SidebarContent,
+ Footer as SidebarFooter,
+ Group as SidebarGroup,
+ GroupAction as SidebarGroupAction,
+ GroupContent as SidebarGroupContent,
+ GroupLabel as SidebarGroupLabel,
+ Header as SidebarHeader,
+ Input as SidebarInput,
+ Inset as SidebarInset,
+ Menu as SidebarMenu,
+ MenuAction as SidebarMenuAction,
+ MenuBadge as SidebarMenuBadge,
+ MenuButton as SidebarMenuButton,
+ MenuItem as SidebarMenuItem,
+ MenuSkeleton as SidebarMenuSkeleton,
+ MenuSub as SidebarMenuSub,
+ MenuSubButton as SidebarMenuSubButton,
+ MenuSubItem as SidebarMenuSubItem,
+ Provider as SidebarProvider,
+ Rail as SidebarRail,
+ Separator as SidebarSeparator,
+ Trigger as SidebarTrigger,
+ Trigger,
+ useSidebar
+};
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-content.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-content.svelte
new file mode 100644
index 0000000..d3c9be2
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-content.svelte
@@ -0,0 +1,24 @@
+
+
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-footer.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-footer.svelte
new file mode 100644
index 0000000..9bad817
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-footer.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-group-action.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-group-action.svelte
new file mode 100644
index 0000000..21f103b
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-group-action.svelte
@@ -0,0 +1,36 @@
+
+
+{#if child}
+ {@render child({ props: propObj })}
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-group-content.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-group-content.svelte
new file mode 100644
index 0000000..38eb796
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-group-content.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-group-label.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-group-label.svelte
new file mode 100644
index 0000000..2abc7d0
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-group-label.svelte
@@ -0,0 +1,34 @@
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-group.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-group.svelte
new file mode 100644
index 0000000..fc5c9e1
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-group.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-header.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-header.svelte
new file mode 100644
index 0000000..b95409c
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-header.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-input.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-input.svelte
new file mode 100644
index 0000000..5a899fc
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-input.svelte
@@ -0,0 +1,23 @@
+
+
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-inset.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-inset.svelte
new file mode 100644
index 0000000..f160f5e
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-inset.svelte
@@ -0,0 +1,24 @@
+
+
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-action.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-action.svelte
new file mode 100644
index 0000000..33f029f
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-action.svelte
@@ -0,0 +1,43 @@
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte
new file mode 100644
index 0000000..0ceff27
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte
@@ -0,0 +1,29 @@
+
+
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-button.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-button.svelte
new file mode 100644
index 0000000..d3e5fbc
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-button.svelte
@@ -0,0 +1,95 @@
+
+
+
+
+{#snippet Button({ props }: { props?: Record })}
+ {@const mergedProps = mergeProps(buttonProps, props)}
+ {#if child}
+ {@render child({ props: mergedProps })}
+ {:else}
+
+ {@render children?.()}
+
+ {/if}
+{/snippet}
+
+{#if !tooltipContent}
+ {@render Button({})}
+{:else}
+
+
+ {#snippet child({ props })}
+ {@render Button({ props })}
+ {/snippet}
+
+
+
+{/if}
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-item.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-item.svelte
new file mode 100644
index 0000000..9d5dab6
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-item.svelte
@@ -0,0 +1,21 @@
+
+
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte
new file mode 100644
index 0000000..6b79f55
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte
@@ -0,0 +1,36 @@
+
+
+
+ {#if showIcon}
+
+ {/if}
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte
new file mode 100644
index 0000000..3fd6b40
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte
@@ -0,0 +1,43 @@
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte
new file mode 100644
index 0000000..a163119
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte
@@ -0,0 +1,14 @@
+
+
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte
new file mode 100644
index 0000000..39c9de3
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte
@@ -0,0 +1,25 @@
+
+
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu.svelte
new file mode 100644
index 0000000..c0d1d7b
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-menu.svelte
@@ -0,0 +1,21 @@
+
+
+
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-provider.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-provider.svelte
new file mode 100644
index 0000000..6cc3ae8
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-provider.svelte
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+ {@render children?.()}
+
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-rail.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-rail.svelte
new file mode 100644
index 0000000..96fc96d
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-rail.svelte
@@ -0,0 +1,36 @@
+
+
+ sidebar.toggle()}
+ title="Toggle Sidebar"
+ class={cn(
+ 'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex',
+ '[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
+ '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
+ 'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar',
+ '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
+ '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-separator.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-separator.svelte
new file mode 100644
index 0000000..94ed358
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-separator.svelte
@@ -0,0 +1,18 @@
+
+
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-trigger.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-trigger.svelte
new file mode 100644
index 0000000..e7a2ba9
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar-trigger.svelte
@@ -0,0 +1,34 @@
+
+
+ {
+ onclick?.(e);
+ sidebar.toggle();
+ }}
+ data-sidebar="trigger"
+ variant="ghost"
+ size="icon"
+ class={cn('h-7 w-7', className)}
+ {...restProps}
+>
+
+ Toggle Sidebar
+
diff --git a/svelte-link-ui/src/lib/components/ui/sidebar/sidebar.svelte b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar.svelte
new file mode 100644
index 0000000..319474e
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/sidebar/sidebar.svelte
@@ -0,0 +1,93 @@
+
+
+{#if collapsible === 'none'}
+
+ {@render children?.()}
+
+{:else if sidebar.isMobile}
+ sidebar.openMobile, (v) => sidebar.setOpenMobile(v)} {...restProps}>
+
+
+{:else}
+
+{/if}
diff --git a/svelte-link-ui/src/lib/components/ui/skeleton/index.ts b/svelte-link-ui/src/lib/components/ui/skeleton/index.ts
new file mode 100644
index 0000000..3120ce1
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/skeleton/index.ts
@@ -0,0 +1,7 @@
+import Root from './skeleton.svelte';
+
+export {
+ Root,
+ //
+ Root as Skeleton
+};
diff --git a/svelte-link-ui/src/lib/components/ui/skeleton/skeleton.svelte b/svelte-link-ui/src/lib/components/ui/skeleton/skeleton.svelte
new file mode 100644
index 0000000..0b62163
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/skeleton/skeleton.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/svelte-link-ui/src/lib/components/ui/tooltip/index.ts b/svelte-link-ui/src/lib/components/ui/tooltip/index.ts
new file mode 100644
index 0000000..034ab20
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/tooltip/index.ts
@@ -0,0 +1,18 @@
+import { Tooltip as TooltipPrimitive } from 'bits-ui';
+import Content from './tooltip-content.svelte';
+
+const Root = TooltipPrimitive.Root;
+const Trigger = TooltipPrimitive.Trigger;
+const Provider = TooltipPrimitive.Provider;
+
+export {
+ Root,
+ Trigger,
+ Content,
+ Provider,
+ //
+ Root as Tooltip,
+ Content as TooltipContent,
+ Trigger as TooltipTrigger,
+ Provider as TooltipProvider
+};
diff --git a/svelte-link-ui/src/lib/components/ui/tooltip/tooltip-content.svelte b/svelte-link-ui/src/lib/components/ui/tooltip/tooltip-content.svelte
new file mode 100644
index 0000000..48dab95
--- /dev/null
+++ b/svelte-link-ui/src/lib/components/ui/tooltip/tooltip-content.svelte
@@ -0,0 +1,21 @@
+
+
+
diff --git a/svelte-link-ui/src/lib/hook/is-mobile.svelte.ts b/svelte-link-ui/src/lib/hook/is-mobile.svelte.ts
new file mode 100644
index 0000000..0f1afe9
--- /dev/null
+++ b/svelte-link-ui/src/lib/hook/is-mobile.svelte.ts
@@ -0,0 +1,9 @@
+import { MediaQuery } from 'svelte/reactivity';
+
+const MOBILE_BREAKPOINT = 768;
+
+export class IsMobile extends MediaQuery {
+ constructor() {
+ super(`max-width: ${MOBILE_BREAKPOINT - 1}px`);
+ }
+}
diff --git a/svelte-link-ui/src/lib/server/user.ts b/svelte-link-ui/src/lib/server/user.ts
index a1498b7..4933461 100644
--- a/svelte-link-ui/src/lib/server/user.ts
+++ b/svelte-link-ui/src/lib/server/user.ts
@@ -1,7 +1,7 @@
import { redis } from './redis';
import { Guid } from 'guid-ts';
import { BACKEND_PROXY_URL } from '$env/static/private';
-import type { UserMeResult } from '../types/user-types';
+import type { UserMeResult } from '../shared-types/user-types';
export async function createUserInCache(user: UserMeResult, expires: Date): Promise {
await redis.set(
diff --git a/svelte-link-ui/src/lib/types/user-types.ts b/svelte-link-ui/src/lib/shared-types/user-types.ts
similarity index 100%
rename from svelte-link-ui/src/lib/types/user-types.ts
rename to svelte-link-ui/src/lib/shared-types/user-types.ts
diff --git a/svelte-link-ui/src/routes/app/+layout.svelte b/svelte-link-ui/src/routes/app/+layout.svelte
new file mode 100644
index 0000000..c8a5cba
--- /dev/null
+++ b/svelte-link-ui/src/routes/app/+layout.svelte
@@ -0,0 +1,20 @@
+
+
+
diff --git a/svelte-link-ui/src/routes/app/+page.server.ts b/svelte-link-ui/src/routes/app/+page.server.ts
index d1c62c9..ed3de4e 100644
--- a/svelte-link-ui/src/routes/app/+page.server.ts
+++ b/svelte-link-ui/src/routes/app/+page.server.ts
@@ -1,6 +1,6 @@
import { redirect } from '@sveltejs/kit';
import { fullLogout } from '$lib/server/current-user';
-import type { UserMeResult } from '$lib/types/user-types';
+import type { UserMeResult } from '$lib/shared-types/user-types';
import type { Actions, RequestEvent } from './$types';
diff --git a/svelte-link-ui/src/routes/app/+page.svelte b/svelte-link-ui/src/routes/app/+page.svelte
index f025115..65a0d54 100644
--- a/svelte-link-ui/src/routes/app/+page.svelte
+++ b/svelte-link-ui/src/routes/app/+page.svelte
@@ -4,19 +4,17 @@
UserMeResult,
AuthorizationLightResult,
RoleLightResult
- } from '$lib/types/user-types';
+ } from '$lib/shared-types/user-types';
- import type { PageData } from './$types';
+ import type { PageProps } from './$types';
import Button from '$lib/components/ui/button/button.svelte';
- import ToggleTheme from '$lib/components/toggle-theme.svelte';
- export let data: PageData;
+ const { data }: PageProps = $props();
const user = data.user as UserMeResult;
Hi, {user.firstname} {user.lastname}!
-
You are connected, and here, you can look at your security info
diff --git a/svelte-link-ui/src/routes/app/chat/+page.server.ts b/svelte-link-ui/src/routes/app/chat/+page.server.ts
new file mode 100644
index 0000000..5446bf2
--- /dev/null
+++ b/svelte-link-ui/src/routes/app/chat/+page.server.ts
@@ -0,0 +1,17 @@
+import type { RequestEvent } from './$types';
+import { type UserMeResult } from '$lib/shared-types/user-types';
+import { BACKEND_PROXY_URL } from '$env/static/private';
+import { getAuthToken } from '$lib/server/backend-token';
+import type { Session } from '$lib/server/session';
+import { error } from '@sveltejs/kit';
+
+export async function load(event: RequestEvent) {
+ const user: UserMeResult = event.locals.user;
+ const session: Session = event.locals.session;
+ const chatUrl: string = `${BACKEND_PROXY_URL}/chathub`;
+
+ return {
+ chatUrl: chatUrl,
+ userName: user.firstname + ' ' + user.lastname
+ };
+}
diff --git a/svelte-link-ui/src/routes/app/chat/+page.svelte b/svelte-link-ui/src/routes/app/chat/+page.svelte
new file mode 100644
index 0000000..ed68218
--- /dev/null
+++ b/svelte-link-ui/src/routes/app/chat/+page.svelte
@@ -0,0 +1,18 @@
+
+
+ getHubToken(tokenType)} {chatUrl} {userName}>
+ {#snippet messageItem(text)}
+
+
{text}
+
{new Date().toLocaleTimeString()}
+
+ {/snippet}
+
diff --git a/svelte-link-ui/src/routes/app/hubtokens/chat/+server.ts b/svelte-link-ui/src/routes/app/hubtokens/chat/+server.ts
new file mode 100644
index 0000000..d5c4373
--- /dev/null
+++ b/svelte-link-ui/src/routes/app/hubtokens/chat/+server.ts
@@ -0,0 +1,34 @@
+import { error } from '@sveltejs/kit';
+import type { RequestHandler } from './$types';
+import { BACKEND_PROXY_URL } from '$env/static/private';
+import { getAuthToken } from '$lib/server/backend-token';
+import type { UserMeResult } from '$lib/shared-types/user-types';
+import type { Session } from '$lib/server/session';
+
+export const GET: RequestHandler = async ({ params, locals }) => {
+ const user: UserMeResult = locals.user;
+ const session: Session = locals.session;
+ const tokens = await getAuthToken(user.authId, session.expiresAt);
+
+ if (!tokens) {
+ error(401, {
+ message: 'Cannot connect to the server'
+ });
+ }
+
+ //TODO: set the type of hub token you want to retrieve
+ const response = await fetch(`${BACKEND_PROXY_URL}/security/api/v1/hubtoken`, {
+ headers: {
+ Authorization: `Bearer ${tokens.accessToken}`
+ }
+ });
+
+ if (!response.ok) {
+ error(400, 'Cannot connect to the server, pls retry or refresh later.');
+ }
+
+ const token: string = await response.json();
+ return new Response(token, {
+ headers: { 'Content-Type': 'text/plain' }
+ });
+};
diff --git a/svelte-link-ui/src/routes/register/+page.server.ts b/svelte-link-ui/src/routes/register/+page.server.ts
index fd0e21e..4141890 100644
--- a/svelte-link-ui/src/routes/register/+page.server.ts
+++ b/svelte-link-ui/src/routes/register/+page.server.ts
@@ -2,7 +2,7 @@ import type { Actions } from './$types';
import type { RequestEvent } from '@sveltejs/kit';
import { getAuthToken } from '$lib/server/backend-token';
import { onboardMe } from '$lib/server/user';
-import type { UserMeResult } from '$lib/types/user-types';
+import type { UserMeResult } from '$lib/shared-types/user-types';
import type { Session } from '$lib/server/session';
import { redirect } from '@sveltejs/kit';
import { fullLogout } from '$lib/server/current-user';
diff --git a/svelte-link-ui/static/fonts/geist.woff2 b/svelte-link-ui/static/fonts/geist.woff2
new file mode 100644
index 0000000..fa004e7
Binary files /dev/null and b/svelte-link-ui/static/fonts/geist.woff2 differ
diff --git a/svelte-link-ui/svelte.txt b/svelte-link-ui/svelte.txt
new file mode 100644
index 0000000..a9ea5e1
--- /dev/null
+++ b/svelte-link-ui/svelte.txt
@@ -0,0 +1,15418 @@
+## docs/svelte/index.md
+
+---
+
+title: Svelte
+---
+
+## docs/svelte/01-introduction/index.md
+
+---
+
+title: Introduction
+---
+
+## docs/svelte/01-introduction/01-overview.md
+
+---
+
+title: Overview
+---
+
+Svelte is a framework for building user interfaces on the web. It uses a compiler to turn declarative components written in HTML, CSS and JavaScript...
+
+```svelte
+
+
+
+click me
+
+
+```
+
+...into lean, tightly optimized JavaScript.
+
+You can use it to build anything on the web, from standalone components to ambitious full stack apps (using Svelte's companion application framework, [SvelteKit](../kit)) and everything in between.
+
+These pages serve as reference documentation. If you're new to Svelte, we recommend starting with the [interactive tutorial](/tutorial) and coming back here when you have questions.
+
+You can also try Svelte online in the [playground](/playground) or, if you need a more fully-featured environment, on [StackBlitz](https://sveltekit.new).
+
+## docs/svelte/01-introduction/02-getting-started.md
+
+---
+
+title: Getting started
+---
+
+We recommend using [SvelteKit](../kit), the official application framework from the Svelte team powered by [Vite](https://vite.dev/):
+
+```bash
+npx sv create myapp
+cd myapp
+npm install
+npm run dev
+```
+
+Don't worry if you don't know Svelte yet! You can ignore all the nice features SvelteKit brings on top for now and dive into it later.
+
+## Alternatives to SvelteKit
+
+You can also use Svelte directly with Vite by running `npm create vite@latest` and selecting the `svelte` option. With this, `npm run build` will generate HTML, JS and CSS files inside the `dist` directory using [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte). In most cases, you will probably need to [choose a routing library](faq#Is-there-a-router) as well.
+
+There are also plugins for [Rollup](https://github.com/sveltejs/rollup-plugin-svelte), [Webpack](https://github.com/sveltejs/svelte-loader) [and a few others](https://sveltesociety.dev/packages?category=build-plugins), but we recommend Vite.
+
+## Editor tooling
+
+The Svelte team maintains a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode), and there are integrations with various other [editors](https://sveltesociety.dev/resources#editor-support) and tools as well.
+
+You can also check your code from the command line using [sv check](https://github.com/sveltejs/cli).
+
+## Getting help
+
+Don't be shy about asking for help in the [Discord chatroom](/chat)! You can also find answers on [Stack Overflow](https://stackoverflow.com/questions/tagged/svelte).
+
+## docs/svelte/01-introduction/03-svelte-files.md
+
+---
+
+title: .svelte files
+---
+
+Components are the building blocks of Svelte applications. They are written into `.svelte` files, using a superset of HTML.
+
+All three sections — script, styles and markup — are optional.
+
+```svelte
+/// file: MyComponent.svelte
+
+
+
+
+
+
+
+```
+
+## `
+
+
+```
+
+You can `export` bindings from this block, and they will become exports of the compiled module. You cannot `export default`, since the default export is the component itself.
+
+
+> [!LEGACY]
+> In Svelte 4, this script tag was created using `
+```
+
+You can specify a fallback value for a prop. It will be used if the component's consumer doesn't specify the prop on the component when instantiating the component, or if the passed value is `undefined` at some point.
+
+```svelte
+
+```
+
+To get all properties, use rest syntax:
+
+```svelte
+
+```
+
+You can use reserved words as prop names.
+
+```svelte
+
+```
+
+If you're using TypeScript, you can declare the prop types:
+
+```svelte
+
+```
+
+If you're using JavaScript, you can declare the prop types using JSDoc:
+
+```svelte
+
+```
+
+If you export a `const`, `class` or `function`, it is readonly from outside the component.
+
+```svelte
+
+```
+
+Readonly props can be accessed as properties on the element, tied to the component using [`bind:this` syntax](bindings#bind:this).
+
+### Reactive variables
+
+To change component state and trigger a re-render, just assign to a locally declared variable that was declared using the `$state` rune.
+
+Update expressions (`count += 1`) and property assignments (`obj.x = y`) have the same effect.
+
+```svelte
+
+```
+
+Svelte's `
+```
+
+If you'd like to react to changes to a prop, use the `$derived` or `$effect` runes instead.
+
+```svelte
+
+```
+
+For more information on reactivity, read the documentation around runes.
+
+## docs/svelte/01-introduction/xx-reactivity-fundamentals.md
+
+---
+
+title: Reactivity fundamentals
+---
+
+Reactivity is at the heart of interactive UIs. When you click a button, you expect some kind of response. It's your job as a developer to make this happen. It's Svelte's job to make your job as intuitive as possible, by providing a good API to express reactive systems.
+
+## Runes
+
+Svelte 5 uses _runes_, a powerful set of primitives for controlling reactivity inside your Svelte components and inside `.svelte.js` and `.svelte.ts` modules.
+
+Runes are function-like symbols that provide instructions to the Svelte compiler. You don't need to import them from anywhere — when you use Svelte, they're part of the language.
+
+The following sections introduce the most important runes for declare state, derived state and side effects at a high level. For more details refer to the later sections on [state](state) and [side effects](side-effects).
+
+## `$state`
+
+Reactive state is declared with the `$state` rune:
+
+```svelte
+
+
+ count++}>
+ clicks: {count}
+
+```
+
+You can also use `$state` in class fields (whether public or private):
+
+```js
+// @errors: 7006 2554
+class Todo {
+ done = $state(false);
+ text = $state();
+
+ constructor(text) {
+ this.text = text;
+ }
+}
+```
+
+> [!LEGACY]
+> In Svelte 4, state was implicitly reactive if the variable was declared at the top level
+>
+> ```svelte
+>
+>
+> count++}>
+> clicks: {count}
+>
+> ```
+
+## `$derived`
+
+Derived state is declared with the `$derived` rune:
+
+```svelte
+
+
+ count++}>
+ {doubled}
+
+
+{count} doubled is {doubled}
+```
+
+The expression inside `$derived(...)` should be free of side-effects. Svelte will disallow state changes (e.g. `count++`) inside derived expressions.
+
+As with `$state`, you can mark class fields as `$derived`.
+
+> [!LEGACY]
+> In Svelte 4, you could use reactive statements for this.
+>
+> ```svelte
+>
+>
+> count++}>
+> {doubled}
+>
+>
+> {count} doubled is {doubled}
+> ```
+>
+> This only worked at the top level of a component.
+
+## `$effect`
+
+To run _side-effects_ when the component is mounted to the DOM, and when values change, we can use the `$effect` rune ([demo](/playground/untitled#H4sIAAAAAAAAE31T24rbMBD9lUG7kAQ2sbdlX7xOYNk_aB_rQhRpbAsU2UiTW0P-vbrYubSlYGzmzMzROTPymdVKo2PFjzMzfIusYB99z14YnfoQuD1qQh-7bmdFQEonrOppVZmKNBI49QthCc-OOOH0LZ-9jxnR6c7eUpOnuv6KeT5JFdcqbvbcBcgDz1jXKGg6ncFyBedYR6IzLrAZwiN5vtSxaJA-EzadfJEjKw11C6GR22-BLH8B_wxdByWpvUYtqqal2XB6RVkG1CoHB6U1WJzbnYFDiwb3aGEdDa3Bm1oH12sQLTcNPp7r56m_00mHocSG97_zd7ICUXonA5fwKbPbkE2ZtMJGGVkEdctzQi4QzSwr9prnFYNk5hpmqVuqPQjNnfOJoMF22lUsrq_UfIN6lfSVyvQ7grB3X2mjMZYO3XO9w-U5iLx42qg29md3BP_ni5P4gy9ikTBlHxjLzAtPDlyYZmRdjAbGq7HprEQ7p64v4LU_guu0kvAkhBim3nMplWl8FreQD-CW20aZR0wq12t-KqDWeBywhvexKC3memmDwlHAv9q4Vo2ZK8KtK0CgX7u9J8wXbzdKv-nRnfF_2baTqlYoWUF2h5efl9-n0O6koAMAAA==)):
+
+```svelte
+
+
+
+```
+
+The function passed to `$effect` will run when the component mounts, and will re-run after any changes to the values it reads that were declared with `$state` or `$derived` (including those passed in with `$props`). Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied.
+
+> [!LEGACY]
+> In Svelte 4, you could use reactive statements for this.
+>
+> ```svelte
+>
+>
+>
+> ```
+>
+> This only worked at the top level of a component.
+
+## docs/svelte/02-runes/index.md
+
+---
+
+title: Runes
+---
+
+## docs/svelte/02-runes/01-what-are-runes.md
+
+---
+
+title: What are runes?
+---
+
+>
+> A letter or mark used as a mystical or magic symbol.
+
+Runes are symbols that you use in `.svelte` and `.svelte.js`/`.svelte.ts` files to control the Svelte compiler. If you think of Svelte as a language, runes are part of the syntax — they are _keywords_.
+
+Runes have a `$` prefix and look like functions:
+
+```js
+let message = $state('hello');
+```
+
+They differ from normal JavaScript functions in important ways, however:
+
+- You don't need to import them — they are part of the language
+- They're not values — you can't assign them to a variable or pass them as arguments to a function
+- Just like JavaScript keywords, they are only valid in certain positions (the compiler will help you if you put them in the wrong place)
+
+> [!LEGACY]
+> Runes didn't exist prior to Svelte 5.
+
+## docs/svelte/02-runes/02-$state.md
+
+---
+
+title: $state
+---
+
+The `$state` rune allows you to create _reactive state_, which means that your UI _reacts_ when it changes.
+
+```svelte
+
+
+ count++}>
+ clicks: {count}
+
+```
+
+Unlike other frameworks you may have encountered, there is no API for interacting with state — `count` is just a number, rather than an object or a function, and you can update it like you would update any other variable.
+
+### Deep state
+
+If `$state` is used with an array or a simple object, the result is a deeply reactive _state proxy_. [Proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) allow Svelte to run code when you read or write properties, including via methods like `array.push(...)`, triggering granular updates.
+
+
+State is proxified recursively until Svelte finds something other than an array or simple object. In a case like this...
+
+```js
+let todos = $state([
+ {
+ done: false,
+ text: 'add more todos'
+ }
+]);
+```
+
+...modifying an individual todo's property will trigger updates to anything in your UI that depends on that specific property:
+
+```js
+let todos = [{ done: false, text: 'add more todos' }];
+//cut
+todos[0].done = !todos[0].done;
+```
+
+If you push a new object to the array, it will also be proxified:
+
+```js
+let todos = [{ done: false, text: 'add more todos' }];
+//cut
+todos.push({
+ done: false,
+ text: 'eat lunch'
+});
+```
+
+
+Note that if you destructure a reactive value, the references are not reactive — as in normal JavaScript, they are evaluated at the point of destructuring:
+
+```js
+let todos = [{ done: false, text: 'add more todos' }];
+//cut
+let { done, text } = todos[0];
+
+// this will not affect the value of `done`
+todos[0].done = !todos[0].done;
+```
+
+### Classes
+
+You can also use `$state` in class fields (whether public or private):
+
+```js
+// @errors: 7006 2554
+class Todo {
+ done = $state(false);
+ text = $state();
+
+ constructor(text) {
+ this.text = text;
+ }
+
+ reset() {
+ this.text = '';
+ this.done = false;
+ }
+}
+```
+
+
+When calling methods in JavaScript, the value of [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) matters. This won't work, because `this` inside the `reset` method will be the `` rather than the `Todo`:
+
+```svelte
+
+ reset
+
+```
+
+You can either use an inline function...
+
+```svelte
+ todo.reset()}>
+ reset
+
+```
+
+...or use an arrow function in the class definition:
+
+```js
+// @errors: 7006 2554
+class Todo {
+ done = $state(false);
+ text = $state();
+
+ constructor(text) {
+ this.text = text;
+ }
+
+ reset = () => {
+ this.text = '';
+ this.done = false;
+ }
+}
+```
+
+## `$state.raw`
+
+In cases where you don't want objects and arrays to be deeply reactive you can use `$state.raw`.
+
+State declared with `$state.raw` cannot be mutated; it can only be _reassigned_. In other words, rather than assigning to a property of an object, or using an array method like `push`, replace the object or array altogether if you'd like to update it:
+
+```js
+let person = $state.raw({
+ name: 'Heraclitus',
+ age: 49
+});
+
+// this will have no effect
+person.age += 1;
+
+// this will work, because we're creating a new person
+person = {
+ name: 'Heraclitus',
+ age: 50
+};
+```
+
+This can improve performance with large arrays and objects that you weren't planning to mutate anyway, since it avoids the cost of making them reactive. Note that raw state can _contain_ reactive state (for example, a raw array of reactive objects).
+
+## `$state.snapshot`
+
+To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`:
+
+```svelte
+
+```
+
+This is handy when you want to pass some state to an external library or API that doesn't expect a proxy, such as `structuredClone`.
+
+## Passing state into functions
+
+JavaScript is a _pass-by-value_ language — when you call a function, the arguments are the _values_ rather than the _variables_. In other words:
+
+```js
+/// file: index.js
+// @filename: index.js
+//cut
+/**
+ * @param {number} a
+ * @param {number} b
+ */
+function add(a, b) {
+ return a + b;
+}
+
+let a = 1;
+let b = 2;
+let total = add(a, b);
+console.log(total); // 3
+
+a = 3;
+b = 4;
+console.log(total); // still 3!
+```
+
+If `add` wanted to have access to the _current_ values of `a` and `b`, and to return the current `total` value, you would need to use functions instead:
+
+```js
+/// file: index.js
+// @filename: index.js
+//cut
+/**
+ * @param {() => number} getA
+ * @param {() => number} getB
+ */
+function add(getA, getB) {
+ return() => getA() + getB();
+}
+
+let a = 1;
+let b = 2;
+let total = add(() => a, () => b);
+console.log(total()); // 3
+
+a = 3;
+b = 4;
+console.log(total()); // 7
+```
+
+State in Svelte is no different — when you reference something declared with the `$state` rune...
+
+```js
+let a =$state(1);
+let b =$state(2);
+```
+
+...you're accessing its _current value_.
+
+Note that 'functions' is broad — it encompasses properties of proxies and [`get`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)/[`set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) properties...
+
+```js
+/// file: index.js
+// @filename: index.js
+//cut
+/**
+ * @param {{ a: number, b: number }} input
+ */
+function add(input) {
+ return {
+ get value() {
+ return input.a + input.b;
+ }
+ };
+}
+
+let input = $state({ a: 1, b: 2 });
+let total = add(input);
+console.log(total.value); // 3
+
+input.a = 3;
+input.b = 4;
+console.log(total.value); // 7
+```
+
+...though if you find yourself writing code like that, consider using [classes](#Classes) instead.
+
+## docs/svelte/02-runes/03-$derived.md
+
+---
+
+title: $derived
+---
+
+Derived state is declared with the `$derived` rune:
+
+```svelte
+
+
+ count++}>
+ {doubled}
+
+
+{count} doubled is {doubled}
+```
+
+The expression inside `$derived(...)` should be free of side-effects. Svelte will disallow state changes (e.g. `count++`) inside derived expressions.
+
+As with `$state`, you can mark class fields as `$derived`.
+
+
+## `$derived.by`
+
+Sometimes you need to create complex derivations that don't fit inside a short expression. In these cases, you can use `$derived.by` which accepts a function as its argument.
+
+```svelte
+
+
+ numbers.push(numbers.length + 1)}>
+ {numbers.join(' + ')} = {total}
+
+```
+
+In essence, `$derived(expression)` is equivalent to `$derived.by(() => expression)`.
+
+## Understanding dependencies
+
+Anything read synchronously inside the `$derived` expression (or `$derived.by` function body) is considered a _dependency_ of the derived state. When the state changes, the derived will be marked as _dirty_ and recalculated when it is next read.
+
+To exempt a piece of state from being treated as a dependency, use [`untrack`](svelte#untrack).
+
+## Update propagation
+
+Svelte uses something called _push-pull reactivity_ — when state is updated, everything that depends on the state (whether directly or indirectly) is immediately notified of the change (the 'push'), but derived values are not re-evaluated until they are actually read (the 'pull').
+
+If the new value of a derived is referentially identical to its previous value, downstream updates will be skipped. In other words, Svelte will only update the text inside the button when `large` changes, not when `count` changes, even though `large` depends on `count`:
+
+```svelte
+
+
+ count++}>
+ {large}
+
+```
+
+## docs/svelte/02-runes/04-$effect.md
+
+---
+
+title: $effect
+---
+
+Effects are what make your application _do things_. When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside [`untrack`](svelte#untrack)), and re-runs the function when that state later changes.
+
+Most of the effects in a Svelte app are created by Svelte itself — they're the bits that update the text in `hello {name}! ` when `name` changes, for example.
+
+But you can also create your own effects with the `$effect` rune, which is useful when you need to synchronize an external system (whether that's a library, or a `` element, or something across a network) with state inside your Svelte app.
+
+
+Your effects run after the component has been mounted to the DOM, and in a [microtask](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) after state changes ([demo](/playground/untitled#H4sIAAAAAAAAE31S246bMBD9lZF3pSRSAqTVvrCAVPUP2sdSKY4ZwJJjkD0hSVH-vbINuWxXfQH5zMyZc2ZmZLVUaFn6a2R06ZGlHmBrpvnBvb71fWQHVOSwPbf4GS46TajJspRlVhjZU1HqkhQSWPkHIYdXS5xw-Zas3ueI6FRn7qHFS11_xSRZhIxbFtcDtw7SJb1iXaOg5XIFeQGjzyPRaevYNOGZIJ8qogbpe8CWiy_VzEpTXiQUcvPDkSVrSNZz1UlW1N5eLcqmpdXUvaQ4BmqlhZNUCgxuzFHDqUWNAxrYeUM76AzsnOsdiJbrBp_71lKpn3RRbii-4P3f-IMsRxS-wcDV_bL4PmSdBa2wl7pKnbp8DMgVvJm8ZNskKRkEM_OzyOKQFkgqOYBQ3Nq89Ns0nbIl81vMFN-jKoLMTOr-SOBOJS-Z8f5Y6D1wdcR8dFqvEBdetK-PHwj-z-cH8oHPY54wRJ8Ys7iSQ3Bg3VA9azQbmC9k35kKzYa6PoVtfwbbKVnBixBiGn7Pq0rqJoUtHiCZwAM3jdTPWCVtr_glhVrhecIa3vuksJ_b7TqFs4DPyriSjd5IwoNNQaAmNI-ESfR2p8zimzvN1swdCkvJHPH6-_oX8o1SgcIDAAA=)):
+
+```svelte
+
+
+
+```
+
+Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied.
+
+You can place `$effect` anywhere, not just at the top level of a component, as long as it is called during component initialization (or while a parent effect is active). It is then tied to the lifecycle of the component (or parent effect) and will therefore destroy itself when the component unmounts (or the parent effect is destroyed).
+
+You can return a function from `$effect`, which will run immediately before the effect re-runs, and before it is destroyed ([demo](/playground/untitled#H4sIAAAAAAAAE42RQY-bMBCF_8rI2kPopiXpMQtIPfbeW6m0xjyKtWaM7CFphPjvFVB2k2oPe7LmzXzyezOjaqxDVKefo5JrD3VaBLVXrLu5-tb3X-IZTmat0hHv6cazgCWqk8qiCbaXouRSHISMH1gop4coWrA7JE9bp7PO2QjjuY5vA8fDYZ3hUh7QNDCy2yWUFzTOUilpSj9aG-linaMKFGACtKCmSwvGGYGeLQvCWbtnMq3m34grajxHoa1JOUXI93_V_Sfz7Oz7Mafj0ypN-zvHm8dSAmQITP_xaUq2IU1GO1dp80I2Uh_82dao92Rl9R8GvgF0QrbrUFstcFeq0PgAkha0LoICPoeB4w1SJUvsZcj4rvcMlvmvGlGCv6J-DeSgw2vabQnJlm55p7nM0rcTctYei3HZxZSl7XHVqkHEM3k2zpqXfFyj393zU05fpyI6f0HI0hUoPoamC9roKDeo2ivBH1EnCQOmX9NfYw2GHrgCAAA=)).
+
+```svelte
+
+
+{count}
+
+ (milliseconds *= 2)}>slower
+ (milliseconds /= 2)}>faster
+```
+
+### Understanding dependencies
+
+`$effect` automatically picks up any reactive values (`$state`, `$derived`, `$props`) that are _synchronously_ read inside its function body (including indirectly, via function calls) and registers them as dependencies. When those dependencies change, the `$effect` schedules a rerun.
+
+Values that are read _asynchronously_ — after an `await` or inside a `setTimeout`, for example — will not be tracked. Here, the canvas will be repainted when `color` changes, but not when `size` changes ([demo](/playground/untitled#H4sIAAAAAAAAE31T246bMBD9lZF3pWSlBEirfaEQqdo_2PatVIpjBrDkGGQPJGnEv1e2IZfVal-wfHzmzJyZ4cIqqdCy9M-F0blDlnqArZjmB3f72XWRHVCRw_bc4me4aDWhJstSlllhZEfbQhekkMDKfwg5PFvihMvX5OXH_CJa1Zrb0-Kpqr5jkiwC48rieuDWQbqgZ6wqFLRcvkC-hYvnkWi1dWqa8ESQTxFRjfQWsOXiWzmr0sSLhEJu3p1YsoJkNUcdZUnN9dagrBu6FVRQHAM10sJRKgUG16bXcGxQ44AGdt7SDkTDdY02iqLHnJVU6hedlWuIp94JW6Tf8oBt_8GdTxlF0b4n0C35ZLBzXb3mmYn3ae6cOW74zj0YVzDNYXRHFt9mprNgHfZSl6mzml8CMoLvTV6wTZIUDEJv5us2iwMtiJRyAKG4tXnhl8O0yhbML0Wm-B7VNlSSSd31BG7z8oIZZ6dgIffAVY_5xdU9Qrz1Bnx8fCfwtZ7v8Qc9j3nB8PqgmMWlHIID6-bkVaPZwDySfWtKNGtquxQ23Qlsq2QJT0KIqb8dL0up6xQ2eIBkAg_c1FI_YqW0neLnFCqFpwmreedJYT7XX8FVOBfwWRhXstZrSXiwKQjUhOZeMIleb5JZfHWn2Yq5pWEpmR7Hv-N_wEqT8hEEAAA=)):
+
+```ts
+// @filename: index.ts
+declare let canvas: {
+ width: number;
+ height: number;
+ getContext(type: '2d', options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
+};
+declare let color: string;
+declare let size: number;
+
+//cut
+$effect(() => {
+ const context = canvas.getContext('2d');
+ context.clearRect(0, 0, canvas.width, canvas.height);
+
+ // this will re-run whenever `color` changes...
+ context.fillStyle = color;
+
+ setTimeout(() => {
+ // ...but not when `size` changes
+ context.fillRect(0, 0, size, size);
+ }, 0);
+});
+```
+
+An effect only reruns when the object it reads changes, not when a property inside it changes. (If you want to observe changes _inside_ an object at dev time, you can use [`$inspect`]($inspect).)
+
+```svelte
+
+
+ (state.value += 1)}>
+ {state.value}
+
+
+{state.value} doubled is {derived.value}
+```
+
+An effect only depends on the values that it read the last time it ran. This has interesting implications for effects that have conditional code.
+
+For instance, if `a` is `true` in the code snippet below, the code inside the `if` block will run and `b` will be evaluated. As such, changes to either `a` or `b` [will cause the effect to re-run](/playground/untitled#H4sIAAAAAAAAE3VQzWrDMAx-FdUU4kBp71li6EPstOxge0ox8-QQK2PD-N1nLy2F0Z2Evj9_chKkP1B04pnYscc3cRCT8xhF95IEf8-Vq0DBr8rzPB_jJ3qumNERH-E2ECNxiRF9tIubWY00lgcYNAywj6wZJS8rtk83wjwgCrXHaULLUrYwKEgVGrnkx-Dx6MNFNstK5OjSbFGbwE0gdXuT_zGYrjmAuco515Hr1p_uXak3K3MgCGS9s-9D2grU-judlQYXIencnzad-tdR79qZrMyvw9wd5Z8Yv1h09dz8mn8AkM7Pfo0BAAA=).
+
+Conversely, if `a` is `false`, `b` will not be evaluated, and the effect will _only_ re-run when `a` changes.
+
+```ts
+let a = false;
+let b = false;
+//cut
+$effect(() => {
+ console.log('running');
+
+ if (a) {
+ console.log('b:', b);
+ }
+});
+```
+
+## `$effect.pre`
+
+In rare cases, you may need to run code _before_ the DOM updates. For this we can use the `$effect.pre` rune:
+
+```svelte
+
+
+
+ {#each messages as message}
+
{message}
+ {/each}
+
+```
+
+Apart from the timing, `$effect.pre` works exactly like `$effect`.
+
+## `$effect.tracking`
+
+The `$effect.tracking` rune is an advanced feature that tells you whether or not the code is running inside a tracking context, such as an effect or inside your template ([demo](/playground/untitled#H4sIAAAAAAAACn3PwYrCMBDG8VeZDYIt2PYeY8Dn2HrIhqkU08nQjItS-u6buAt7UDzmz8ePyaKGMWBS-nNRcmdU-hHUTpGbyuvI3KZvDFLal0v4qvtIgiSZUSb5eWSxPfWSc4oB2xDP1XYk8HHiSHkICeXKeruDDQ4Demlldv4y0rmq6z10HQwuJMxGVv4mVVXDwcJS0jP9u3knynwtoKz1vifT_Z9Jhm0WBCcOTlDD8kyspmML5qNpHg40jc3fFryJ0iWsp_UHgz3180oBAAA=)):
+
+```svelte
+
+
+in template: {$effect.tracking()}
+```
+
+It is used to implement abstractions like [`createSubscriber`](/docs/svelte/svelte-reactivity#createSubscriber), which will create listeners to update reactive values but _only_ if those values are being tracked (rather than, for example, read inside an event handler).
+
+## `$effect.root`
+
+The `$effect.root` rune is an advanced feature that creates a non-tracked scope that doesn't auto-cleanup. This is useful for nested effects that you want to manually control. This rune also allows for the creation of effects outside of the component initialisation phase.
+
+```svelte
+
+```
+
+## When not to use `$effect`
+
+In general, `$effect` is best considered something of an escape hatch — useful for things like analytics and direct DOM manipulation — rather than a tool you should use frequently. In particular, avoid using it to synchronise state. Instead of this...
+
+```svelte
+
+```
+
+...do this:
+
+```svelte
+
+```
+
+
+You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for "money spent" and "money left" that are connected to each other. If you update one, the other should update accordingly. Don't use effects for this ([demo](/playground/untitled#H4sIAAAAAAAACpVRy26DMBD8FcvKgUhtoIdeHBwp31F6MGSJkBbHwksEQvx77aWQqooq9bgzOzP7mGTdIHipPiZJowOpGJAv0po2VmfnDv4OSBErjYdneHWzBJaCjcx91TWOToUtCIEE3cig0OIty44r5l1oDtjOkyFIsv3GINQ_CNYyGegd1DVUlCR7oU9iilDUcP8S8roYs9n8p2wdYNVFm4csTx872BxNCcjr5I11fdgonEkXsjP2CoUUZWMv6m6wBz2x7yxaM-iJvWeRsvSbSVeUy5i0uf8vKA78NIeJLSZWv1I8jQjLdyK4XuTSeIdmVKJGGI4LdjVOiezwDu1yG74My8PLCQaSiroe5s_5C2PHrkVGAgAA)):
+
+```svelte
+
+
+
+
+ {spent}/{total} spent
+
+
+
+
+ {left}/{total} left
+
+```
+
+Instead, use callbacks where possible ([demo](/playground/untitled#H4sIAAAAAAAACo1SMW6EMBD8imWluFMSIEUaDiKlvy5lSOHjlhOSMRZeTiDkv8deMEEJRcqdmZ1ZjzzxqpZgePo5cRw18JQA_sSVaPz0rnVk7iDRYxdhYA8vW4Wg0NnwzJRdrfGtUAVKQIYtCsly9pIkp4AZ7cQOezAoEA7JcWUkVBuCdol0dNWrEutWsV5fHfnhPQ5wZJMnCwyejxCh6G6A0V3IHk4zu_jOxzzPBxBld83PTr7xXrb3rUNw8PbiYJ3FP22oTIoLSComq5XuXTeu8LzgnVA3KDgj13wiQ8taRaJ82rzXskYM-URRlsXktejjgNLoo9e4fyf70_8EnwncySX1GuunX6kGRwnzR_BgaPNaGy3FmLJKwrCUeBM6ZUn0Cs2mOlp3vwthQJ5i14P9st9vZqQlsQIAAA==)):
+
+```svelte
+
+
+
+
+ {spent}/{total} spent
+
+
+
+
+ {left}/{total} left
+
+```
+
+If you need to use bindings, for whatever reason (for example when you want some kind of "writable `$derived`"), consider using getters and setters to synchronise state ([demo](/playground/untitled#H4sIAAAAAAAACpWRwW6DMBBEf8WyekikFOihFwcq9TvqHkyyQUjGsfCCQMj_XnvBNKpy6Qn2DTOD1wu_tRocF18Lx9kCFwT4iRvVxenT2syNoDGyWjl4xi93g2AwxPDSXfrW4oc0EjUgwzsqzSr2VhTnxJwNHwf24lAhHIpjVDZNwy1KS5wlNoGMSg9wOCYksQccerMlv65p51X0p_Xpdt_4YEy9yTkmV3z4MJT579-bUqsaNB2kbI0dwlnCgirJe2UakJzVrbkKaqkWivasU1O1ULxnOVk3JU-Uxti0p_-vKO4no_enbQ_yXhnZn0aHs4b1jiJMK7q2zmo1C3bTMG3LaZQVrMjeoSPgaUtkDxePMCEX2Ie6b_8D4WyJJEwCAAA=)):
+
+```svelte
+
+
+
+
+ {spent}/{total} spent
+
+
+
+
+ {left.value}/{total} left
+
+```
+
+If you absolutely have to update `$state` within an effect and run into an infinite loop because you read and write to the same `$state`, use [untrack](svelte#untrack).
+
+## docs/svelte/02-runes/05-$props.md
+
+---
+
+title: $props
+---
+
+The inputs to a component are referred to as _props_, which is short for _properties_. You pass props to components just like you pass attributes to elements:
+
+```svelte
+
+
+
+
+```
+
+On the other side, inside `MyComponent.svelte`, we can receive props with the `$props` rune...
+
+```svelte
+
+
+
+this component is {props.adjective}
+```
+
+...though more commonly, you'll [_destructure_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) your props:
+
+```svelte
+
+
+
+this component is {adjective}
+```
+
+## Fallback values
+
+Destructuring allows us to declare fallback values, which are used if the parent component does not set a given prop:
+
+```js
+let { adjective = 'happy' } = $props();
+```
+
+
+## Renaming props
+
+We can also use the destructuring assignment to rename props, which is necessary if they're invalid identifiers, or a JavaScript keyword like `super`:
+
+```js
+let { super: trouper = 'lights are gonna find me' } = $props();
+```
+
+## Rest props
+
+Finally, we can use a _rest property_ to get, well, the rest of the props:
+
+```js
+let { a, b, c, ...others } = $props();
+```
+
+## Updating props
+
+References to a prop inside a component update when the prop itself updates — when `count` changes in `App.svelte`, it will also change inside `Child.svelte`. But the child component is able to temporarily override the prop value, which can be useful for unsaved ephemeral state ([demo](/playground/untitled#H4sIAAAAAAAAE6WQ0WrDMAxFf0WIQR0Wmu3VTQJln7HsIfVcZubIxlbGRvC_DzuBraN92qPula50tODZWB1RPi_IX16jLALWSOOUq6P3-_ihLWftNEZ9TVeOWBNHlNhGFYznfqCBzeRdYHh6M_YVzsFNsNs3pdpGd4eBcqPVDMrNxNDBXeSRtXioDgO1zU8ataeZ2RE4Utao924RFXQ9iHXwvoPHKpW1xY4g_Bg0cSVhKS0p560Za95612ZC02ONrD8ZJYdZp_rGQ37ff_mSP86Np2TWZaNNmdcH56P4P67K66_SXoK9pG-5dF5Z9QEAAA==)):
+
+```svelte
+
+
+
+ (count += 1)}>
+ clicks (parent): {count}
+
+
+
+```
+
+```svelte
+
+
+
+ (count += 1)}>
+ clicks (child): {count}
+
+```
+
+While you can temporarily _reassign_ props, you should not _mutate_ props unless they are [bindable]($bindable).
+
+If the prop is a regular object, the mutation will have no effect ([demo](/playground/untitled#H4sIAAAAAAAAE3WQwU7DMBBEf2W1QmorQgJXk0RC3PkBwiExG9WQrC17U4Es_ztKUkQp9OjxzM7bjcjtSKjwyfKNp1aLORA4b13ADHszUED1HFE-3eyaBcy-Mw_O5eFAg8xa1wb6T9eWhVgCKiyD9sZJ3XAjZnTWCzzuzfAKvbcjbPJieR2jm_uGy-InweXqtd0baaliBG0nFgW3kBIUNWYo9CGoxE-UsgvIpw2_oc9-LmAPJBCPDJCggqvlVtvdH9puErEMlvVg9HsVtzuoaojzkKKAfRuALVDfk5ZZW0fmy05wXcFdwyktlUs-KIinljTXrRVnm7-kL9dYLVbUAQAA)):
+
+```svelte
+
+
+
+
+```
+
+```svelte
+
+
+
+ {
+ // has no effect
+ object.count += 1
+}}>
+ clicks: {object.count}
+
+```
+
+If the prop is a reactive state proxy, however, then mutations _will_ have an effect but you will see an [`ownership_invalid_mutation`](runtime-warnings#Client-warnings-ownership_invalid_mutation) warning, because the component is mutating state that does not 'belong' to it ([demo](/playground/untitled#H4sIAAAAAAAAE3WR0U7DMAxFf8VESBuiauG1WycheOEbKA9p67FA6kSNszJV-XeUZhMw2GN8r-1znUmQ7FGU4pn2UqsOes-SlSGRia3S6ET5Mgk-2OiJBZGdOh6szd0eNcdaIx3-V28NMRI7UYq1awdleVNTzaq3ZmB43CndwXYwPSzyYn4dWxermqJRI4Np3rFlqODasWRcTtAaT1zCHYSbVU3r4nsyrdPMKTUFKDYiE4yfLEoePIbsQpqfy3_nOVMuJIqg0wk1RFg7GOuWfwEbz2wIDLVatR_VtLyBagNTHFIUMCqtoZXeIfAOU1JoUJsR2IC3nWTMjt7GM4yKdyBhlAMpesvhydCC0y_i0ZagHByMh26WzUhXUUxKnpbcVnBfUwhznJnNlac7JkuIURL-2VVfwxflyrWcSQIAAA==)):
+
+```svelte
+
+
+
+
+```
+
+```svelte
+
+
+
+ {
+ // will cause the count below to update,
+ // but with a warning. Don't mutate
+ // objects you don't own!
+ object.count += 1
+}}>
+ clicks: {object.count}
+
+```
+
+The fallback value of a prop not declared with `$bindable` is left untouched — it is not turned into a reactive state proxy — meaning mutations will not cause updates ([demo](/playground/untitled#H4sIAAAAAAAAE3WQwU7DMBBEf2VkIbUVoYFraCIh7vwA4eC4G9Wta1vxpgJZ_nfkBEQp9OjxzOzTRGHlkUQlXpy9G0gq1idCL43ppDrAD84HUYheGwqieo2CP3y2Z0EU3-En79fhRIaz1slA_-nKWSbLQVRiE9SgPTetbVkfvRsYzztttugHd8RiXU6vr-jisbWb8idhN7O3bEQhmN5ZVDyMlIorcOddv_Eufq4AGmJEuG5PilEjQrnRcoV7JCTUuJlGWq7-YHYjs7NwVhmtDnVcrlA3iLmzLLGTAdaB-j736h68Oxv-JM1I0AFjoG1OzPfX023c1nhobUoT39QeKsRzS8owM8DFTG_pE6dcVl70AQAA))
+
+```svelte
+
+
+
+ {
+ // has no effect if the fallback value is used
+ object.count += 1
+}}>
+ clicks: {object.count}
+
+```
+
+In summary: don't mutate props. Either use callback props to communicate changes, or — if parent and child should share the same object — use the [`$bindable`]($bindable) rune.
+
+## Type safety
+
+You can add type safety to your components by annotating your props, as you would with any other variable declaration. In TypeScript that might look like this...
+
+```svelte
+
+```
+
+...while in JSDoc you can do this:
+
+```svelte
+
+```
+
+You can, of course, separate the type declaration from the annotation:
+
+```svelte
+
+```
+
+
+Adding types is recommended, as it ensures that people using your component can easily discover which props they should provide.
+
+
+## `$props.id()`
+
+This rune, added in version 5.20.0, generates an ID that is unique to the current component instance. When hydrating a server-rendered component, the value will be consistent between server and client.
+
+This is useful for linking elements via attributes like `for` and `aria-labelledby`.
+
+```svelte
+
+
+
+```
+
+## docs/svelte/02-runes/06-$bindable.md
+
+---
+
+title: $bindable
+---
+
+Ordinarily, props go one way, from parent to child. This makes it easy to understand how data flows around your app.
+
+In Svelte, component props can be _bound_, which means that data can also flow _up_ from child to parent. This isn't something you should do often, but it can simplify your code if used sparingly and carefully.
+
+It also means that a state proxy can be _mutated_ in the child.
+
+
+To mark a prop as bindable, we use the `$bindable` rune:
+
+```svelte
+/// file: FancyInput.svelte
+
+
+
+
+
+```
+
+Now, a component that uses `` can add the [`bind:`](bind) directive ([demo](/playground/untitled#H4sIAAAAAAAAE3WQwWrDMBBEf2URBSfg2nfFMZRCoYeecqx6UJx1IyqvhLUONcb_XqSkTUOSk1az7DBvJtEai0HI90nw6FHIJIhckO7i78n7IhzQctS2OuAtvXHESByEFFVoeuO5VqTYdN71DC-amvGV_MDQ9q6DrCjP0skkWymKJxYZOgxBfyKs4SGwZlxke7TWZcuVoqo8-1P1z3lraCcP2g64nk4GM5S1osrXf0JV-lrkgvGbheR-wDm_g30V8JL-1vpOCZFogpQsEsWcemtxscyhKArfOx9gjps0Lq4hzRVfemaYfu-PoIqqwKPFY_XpaIqj4tYRP7a6M3aUkD27zjSw0RTgbZN6Z8WNs66XsEP03tBXUueUJFlelvYx_wCuI3leNwIAAA==)):
+
+```svelte
+/// file: App.svelte
+
+
+
+{message}
+```
+
+The parent component doesn't _have_ to use `bind:` — it can just pass a normal prop. Some parents don't want to listen to what their children have to say.
+
+In this case, you can specify a fallback value for when no prop is passed at all:
+
+```js
+/// file: FancyInput.svelte
+let { value = $bindable('fallback'), ...props } = $props();
+```
+
+## docs/svelte/02-runes/07-$inspect.md
+
+---
+
+title: $inspect
+---
+
+
+The `$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 ([demo](/playground/untitled#H4sIAAAAAAAACkWQ0YqDQAxFfyUMhSotdZ-tCvu431AXtGOqQ2NmmMm0LOK_r7Utfby5JzeXTOpiCIPKT5PidkSVq2_n1F7Jn3uIcEMSXHSw0evHpAjaGydVzbUQCmgbWaCETZBWMPlKj29nxBDaHj_edkAiu12JhdkYDg61JGvE_s2nR8gyuBuiJZuDJTyQ7eE-IEOzog1YD80Lb0APLfdYc5F9qnFxjiKWwbImo6_llKRQVs-2u91c_bD2OCJLkT3JZasw7KLA2XCX31qKWE6vIzNk1fKE0XbmYrBTufiI8-_8D2cUWBA_AQAA)):
+
+```svelte
+
+
+ count++}>Increment
+
+```
+
+## $inspect(...).with
+
+`$inspect` returns a property `with`, which you can invoke with a callback, which 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` ([demo](/playground/untitled#H4sIAAAAAAAACkVQ24qDMBD9lSEUqlTqPlsj7ON-w7pQG8c2VCchmVSK-O-bKMs-DefKYRYx6BG9qL4XQd2EohKf1opC8Nsm4F84MkbsTXAqMbVXTltuWmp5RAZlAjFIOHjuGLOP_BKVqB00eYuKs82Qn2fNjyxLtcWeyUE2sCRry3qATQIpJRyD7WPVMf9TW-7xFu53dBcoSzAOrsqQNyOe2XUKr0Xi5kcMvdDB2wSYO-I9vKazplV1-T-d6ltgNgSG1KjVUy7ZtmdbdjqtzRcphxMS1-XubOITJtPrQWMvKnYB15_1F7KKadA_AQAA)):
+
+```svelte
+
+
+ count++}>Increment
+```
+
+A convenient way to find the origin of some change is to pass `console.trace` to `with`:
+
+```js
+// @errors: 2304
+$inspect(stuff).with(console.trace);
+```
+
+## $inspect.trace(...)
+
+This 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]($effect) or a [derived]($derived), information will be printed to the console about which pieces of reactive state caused the effect to fire.
+
+```svelte
+
+```
+
+`$inspect.trace` takes an optional first argument which will be used as the label.
+
+## docs/svelte/02-runes/08-$host.md
+
+---
+
+title: $host
+---
+
+When compiling a component as a custom element, the `$host` rune provides access to the host element, allowing you to (for example) dispatch custom events ([demo](/playground/untitled#H4sIAAAAAAAAE41Ry2rDMBD8FSECtqkTt1fHFpSSL-ix7sFRNkTEXglrnTYY_3uRlDgxTaEHIfYxs7szA9-rBizPPwZOZwM89wmecqxbF70as7InaMjltrWFR3mpkQDJ8pwXVnbKkKiwItUa3RGLVtk7gTHQXRDR2lXda4CY1D0SK9nCUk0QPyfrCovsRoNFe17aQOAwGncgO2gBqRzihJXiQrEs2csYOhQ-7HgKHaLIbpRhhBG-I2eD_8ciM4KnnOCbeE5dD2P6h0Dz0-Yi_arNhPLJXBtSGi2TvSXdbpqwdsXvjuYsC1veabvvUTog2ylrapKH2G2XsMFLS4uDthQnq2t1cwKkGOGLvYU5PvaQxLsxOkPmsm97Io1Mo2yUPF6VnOZFkw1RMoopKLKAE_9gmGxyDFMwMcwN-Bx_ABXQWmOtAgAA)):
+
+```svelte
+/// file: Stepper.svelte
+
+
+
+
+ dispatch('decrement')}>decrement
+ dispatch('increment')}>increment
+```
+
+```svelte
+/// file: App.svelte
+
+
+ count -= 1}
+ onincrement={() => count += 1}
+>
+
+count: {count}
+```
+
+## docs/svelte/03-template-syntax/index.md
+
+---
+
+title: Template syntax
+---
+
+## docs/svelte/03-template-syntax/01-basic-markup.md
+
+---
+
+title: Basic markup
+---
+
+Markup inside a Svelte component can be thought of as HTML++.
+
+## Tags
+
+A lowercase tag, like ``, denotes a regular HTML element. A capitalised tag or a tag that uses dot notation, such as `
` or ``, indicates a _component_.
+
+```svelte
+
+
+
+
+
+```
+
+## Element attributes
+
+By default, attributes work exactly like their HTML counterparts.
+
+```svelte
+
+ can't touch this
+
+```
+
+As in HTML, values may be unquoted.
+
+```svelte
+
+```
+
+Attribute values can contain JavaScript expressions.
+
+```svelte
+page {p}
+```
+
+Or they can _be_ JavaScript expressions.
+
+```svelte
+...
+```
+
+Boolean attributes are included on the element if their value is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) and excluded if it's [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy).
+
+All other attributes are included unless their value is [nullish](https://developer.mozilla.org/en-US/docs/Glossary/Nullish) (`null` or `undefined`).
+
+```svelte
+
+This div has no title attribute
+```
+
+>
+>
+> ```svelte
+> ...
+> ```
+
+When the attribute name and value match (`name={name}`), they can be replaced with `{name}`.
+
+```svelte
+...
+
+```
+
+## Component props
+
+By convention, values passed to components are referred to as _properties_ or _props_ rather than _attributes_, which are a feature of the DOM.
+
+As with elements, `name={name}` can be replaced with the `{name}` shorthand.
+
+```svelte
+
+```
+
+_Spread attributes_ allow many attributes or properties to be passed to an element or component at once.
+
+An element or component can have multiple spread attributes, interspersed with regular ones.
+
+```svelte
+
+```
+
+## Events
+
+Listening to DOM events is possible by adding attributes to the element that start with `on`. For example, to listen to the `click` event, add the `onclick` attribute to a button:
+
+```svelte
+ console.log('clicked')}>click me
+```
+
+Event attributes are case sensitive. `onclick` listens to the `click` event, `onClick` listens to the `Click` event, which is different. This ensures you can listen to custom events that have uppercase characters in them.
+
+Because events are just attributes, the same rules as for attributes apply:
+
+- you can use the shorthand form: `click me `
+- you can spread them: `click me `
+
+Timing-wise, event attributes always fire after events from bindings (e.g. `oninput` always fires after an update to `bind:value`). Under the hood, some event handlers are attached directly with `addEventListener`, while others are _delegated_.
+
+When using `ontouchstart` and `ontouchmove` event attributes, the handlers are [passive](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners) for better performance. This greatly improves responsiveness by allowing the browser to scroll the document immediately, rather than waiting to see if the event handler calls `event.preventDefault()`.
+
+In the very rare cases that you need to prevent these event defaults, you should use [`on`](svelte-events#on) instead (for example inside an action).
+
+### Event delegation
+
+To reduce memory footprint and increase performance, Svelte uses a technique called event delegation. This means that for certain events — see the list below — a single event listener at the application root takes responsibility for running any handlers on the event's path.
+
+There are a few gotchas to be aware of:
+
+- when you manually dispatch an event with a delegated listener, make sure to set the `{ bubbles: true }` option or it won't reach the application root
+- when using `addEventListener` directly, avoid calling `stopPropagation` or the event won't reach the application root and handlers won't be invoked. Similarly, handlers added manually inside the application root will run _before_ handlers added declaratively deeper in the DOM (with e.g. `onclick={...}`), in both capturing and bubbling phases. For these reasons it's better to use the `on` function imported from `svelte/events` rather than `addEventListener`, as it will ensure that order is preserved and `stopPropagation` is handled correctly.
+
+The following event handlers are delegated:
+
+- `beforeinput`
+- `click`
+- `change`
+- `dblclick`
+- `contextmenu`
+- `focusin`
+- `focusout`
+- `input`
+- `keydown`
+- `keyup`
+- `mousedown`
+- `mousemove`
+- `mouseout`
+- `mouseover`
+- `mouseup`
+- `pointerdown`
+- `pointermove`
+- `pointerout`
+- `pointerover`
+- `pointerup`
+- `touchend`
+- `touchmove`
+- `touchstart`
+
+## Text expressions
+
+A JavaScript expression can be included as text by surrounding it with curly braces.
+
+```svelte
+{expression}
+```
+
+Curly braces can be included in a Svelte template by using their [HTML entity](https://developer.mozilla.org/docs/Glossary/Entity) strings: `{`, `{`, or `{` for `{` and `}`, `}`, or `}` for `}`.
+
+If you're using a regular expression (`RegExp`) [literal notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#literal_notation_and_constructor), you'll need to wrap it in parentheses.
+
+```svelte
+Hello {name}!
+{a} + {b} = {a + b}.
+
+{(/^[A-Za-z ]+$/).test(value) ? x : y}
+```
+
+The expression will be stringified and escaped to prevent code injections. If you want to render HTML, use the `{@html}` tag instead.
+
+```svelte
+{@html potentiallyUnsafeHtmlString}
+```
+
+
+## Comments
+
+You can use HTML comments inside components.
+
+```svelte
+Hello world
+```
+
+Comments beginning with `svelte-ignore` disable warnings for the next block of markup. Usually, these are accessibility warnings; make sure that you're disabling them for a good reason.
+
+```svelte
+
+
+```
+
+You can add a special comment starting with `@component` that will show up when hovering over the component name in other files.
+
+````svelte
+
+
+
+
+
+ Hello, {name}
+
+
+````
+
+## docs/svelte/03-template-syntax/02-if.md
+
+---
+
+title: {#if ...}
+---
+
+```svelte
+
+{#if expression}...{/if}
+```
+
+```svelte
+
+{#if expression}...{:else if expression}...{/if}
+```
+
+```svelte
+
+{#if expression}...{:else}...{/if}
+```
+
+Content that is conditionally rendered can be wrapped in an if block.
+
+```svelte
+{#if answer === 42}
+ what was the question?
+{/if}
+```
+
+Additional conditions can be added with `{:else if expression}`, optionally ending in an `{:else}` clause.
+
+```svelte
+{#if porridge.temperature > 100}
+ too hot!
+{:else if 80 > porridge.temperature}
+ too cold!
+{:else}
+ just right!
+{/if}
+```
+
+(Blocks don't have to wrap elements, they can also wrap text within elements.)
+
+## docs/svelte/03-template-syntax/03-each.md
+
+---
+
+title: {#each ...}
+---
+
+```svelte
+
+{#each expression as name}...{/each}
+```
+
+```svelte
+
+{#each expression as name, index}...{/each}
+```
+
+Iterating over values can be done with an each block. The values in question can be arrays, array-like objects (i.e. anything with a `length` property), or iterables like `Map` and `Set` — in other words, anything that can be used with `Array.from`.
+
+```svelte
+Shopping list
+
+ {#each items as item}
+ {item.name} x {item.qty}
+ {/each}
+
+```
+
+An each block can also specify an _index_, equivalent to the second argument in an `array.map(...)` callback:
+
+```svelte
+{#each items as item, i}
+ {i + 1}: {item.name} x {item.qty}
+{/each}
+```
+
+## Keyed each blocks
+
+```svelte
+
+{#each expression as name (key)}...{/each}
+```
+
+```svelte
+
+{#each expression as name, index (key)}...{/each}
+```
+
+If a _key_ expression is provided — which must uniquely identify each list item — Svelte will use it to diff the list when data changes, rather than adding or removing items at the end. The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change.
+
+```svelte
+{#each items as item (item.id)}
+ {item.name} x {item.qty}
+{/each}
+
+
+{#each items as item, i (item.id)}
+ {i + 1}: {item.name} x {item.qty}
+{/each}
+```
+
+You can freely use destructuring and rest patterns in each blocks.
+
+```svelte
+{#each items as { id, name, qty }, i (id)}
+ {i + 1}: {name} x {qty}
+{/each}
+
+{#each objects as { id, ...rest }}
+ {id}
+{/each}
+
+{#each items as [id, ...rest]}
+ {id}
+{/each}
+```
+
+## Each blocks without an item
+
+```svelte
+
+{#each expression}...{/each}
+```
+
+```svelte
+
+{#each expression, index}...{/each}
+```
+
+In case you just want to render something `n` times, you can omit the `as` part ([demo](/playground/untitled#H4sIAAAAAAAAE3WR0W7CMAxFf8XKNAk0WsSeUEaRpn3Guoc0MbQiJFHiMlDVf18SOrZJ48259_jaVgZmxBEZZ28thgCNFV6xBdt1GgPj7wOji0t2EqI-wa_OleGEmpLWiID_6dIaQkMxhm1UdwKpRQhVzWSaVORJNdvWpqbhAYVsYQCNZk8thzWMC_DCHMZk3wPSThNQ088I3mghD9UwSwHwlLE5PMIzVFUFq3G7WUZ2OyUvU3JOuZU332wCXTRmtPy1NgzXZtUFp8WFw9536uWqpbIgPEaDsJBW90cTOHh0KGi2XsBq5-cT6-3nPauxXqHnsHJnCFZ3CvJVkyuCQ0mFF9TZyCQ162WGvteLKfG197Y3iv_pz_fmS68Hxt8iPBPj5HscP8YvCNX7uhYCAAA=)):
+
+```svelte
+
+ {#each { length: 8 }, rank}
+ {#each { length: 8 }, file}
+
+ {/each}
+ {/each}
+
+```
+
+## Else blocks
+
+```svelte
+
+{#each expression as name}...{:else}...{/each}
+```
+
+An each block can also have an `{:else}` clause, which is rendered if the list is empty.
+
+```svelte
+{#each todos as todo}
+ {todo.text}
+{:else}
+ No tasks today!
+{/each}
+```
+
+## docs/svelte/03-template-syntax/04-key.md
+
+---
+
+title: {#key ...}
+---
+
+```svelte
+
+{#key expression}...{/key}
+```
+
+Key blocks destroy and recreate their contents when the value of an expression changes. When used around components, this will cause them to be reinstantiated and reinitialised:
+
+```svelte
+{#key value}
+
+{/key}
+```
+
+It's also useful if you want a transition to play whenever a value changes:
+
+```svelte
+{#key value}
+ {value}
+{/key}
+```
+
+## docs/svelte/03-template-syntax/05-await.md
+
+---
+
+title: {#await ...}
+---
+
+```svelte
+
+{#await expression}...{:then name}...{:catch name}...{/await}
+```
+
+```svelte
+
+{#await expression}...{:then name}...{/await}
+```
+
+```svelte
+
+{#await expression then name}...{/await}
+```
+
+```svelte
+
+{#await expression catch name}...{/await}
+```
+
+Await blocks allow you to branch on the three possible states of a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) — pending, fulfilled or rejected.
+
+```svelte
+{#await promise}
+
+ waiting for the promise to resolve...
+{:then value}
+
+ The value is {value}
+{:catch error}
+
+ Something went wrong: {error.message}
+{/await}
+```
+
+>
+> If the provided expression is not a `Promise`, only the `:then` branch will be rendered, including during server-side rendering.
+
+The `catch` block can be omitted if you don't need to render anything when the promise rejects (or no error is possible).
+
+```svelte
+{#await promise}
+
+ waiting for the promise to resolve...
+{:then value}
+
+ The value is {value}
+{/await}
+```
+
+If you don't care about the pending state, you can also omit the initial block.
+
+```svelte
+{#await promise then value}
+ The value is {value}
+{/await}
+```
+
+Similarly, if you only want to show the error state, you can omit the `then` block.
+
+```svelte
+{#await promise catch error}
+ The error is {error}
+{/await}
+```
+
+>
+> ```svelte
+> {#await import('./Component.svelte') then { default: Component }}
+>
+> {/await}
+> ```
+
+## docs/svelte/03-template-syntax/06-snippet.md
+
+---
+
+title: {#snippet ...}
+---
+
+```svelte
+
+{#snippet name()}...{/snippet}
+```
+
+```svelte
+
+{#snippet name(param1, param2, paramN)}...{/snippet}
+```
+
+Snippets, and [render tags](@render), are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like [this](/playground/untitled#H4sIAAAAAAAAE5VUYW-kIBD9K8Tmsm2yXXRzvQ-s3eR-R-0HqqOQKhAZb9sz_vdDkV1t000vRmHewMx7w2AflbIGG7GnPlK8gYhFv42JthG-m9Gwf6BGcLbVXZuPSGrzVho8ZirDGpDIhldgySN5GpEMez9kaNuckY1ANJZRamRuu2ZnhEZt6a84pvs43mzD4pMsUDDi8DMkQFYCGdkvsJwblFq5uCik9bmJ4JZwUkv1eoknWigX2eGNN6aGXa6bjV8ybP-X7sM36T58SVcrIIV2xVIaA41xeD5kKqWXuqpUJEefOqVuOkL9DfBchGrzWfu0vb-RpTd3o-zBR045Ga3HfuE5BmJpKauuhbPtENlUF2sqR9jqpsPSxWsMrlngyj3VJiyYjJXb1-lMa7IWC-iSk2M5Zzh-SJjShe-siq5kpZRPs55BbSGU5YPyte4vVV_VfFXxVb10dSLf17pS2lM5HnpPxw4Zpv6x-F57p0jI3OKlVnhv5V9wPQrNYQQ9D_f6aGHlC89fq1Z3qmDkJCTCweOGF4VUFSPJvD_DhreVdA0eu8ehJJ5x91dBaBkpWm3ureCFPt3uzRv56d4kdp-2euG38XZ6dsnd3ZmPG9yRBCrzRUvi-MccOdwz3qE-fOZ7AwAhlrtTUx3c76vRhSwlFBHDtoPhefgHX3dM0PkEAAA=)...
+
+```svelte
+{#each images as image}
+ {#if image.href}
+
+
+
+ {image.caption}
+
+
+ {:else}
+
+
+ {image.caption}
+
+ {/if}
+{/each}
+```
+
+...you can write [this](/playground/untitled#H4sIAAAAAAAAE5VUYW-bMBD9KxbRlERKY4jWfSA02n5H6QcXDmwVbMs-lnaI_z6D7TTt1moTAnPvzvfenQ_GpBEd2CS_HxPJekjy5IfWyS7BFz0b9id0CM62ajDVjBS2MkLjqZQldoBE9KwFS-7I_YyUOPqlRGuqnKw5orY5pVpUduj3mitUln5LU3pI0_UuBp9FjTwnDr9AHETLMSeHK6xiGoWSLi9yYT034cwSRjohn17zcQPNFTs8s153sK9Uv_Yh0-5_5d7-o9zbD-UqCaRWrllSYZQxLw_HUhb0ta-y4NnJUxfUvc7QuLJSaO0a3oh2MLBZat8u-wsPnXzKQvTtVVF34xK5d69ThFmHEQ4SpzeVRediTG8rjD5vBSeN3E5JyHh6R1DQK9-iml5kjzQUN_lSgVU8DhYLx7wwjSvRkMDvTjiwF4zM1kXZ7DlF1eN3A7IG85e-zRrYEjjm0FkI4Cc7Ripm0pHOChexhcWXzreeZyRMU6Mk3ljxC9w4QH-cQZ_b3T5pjHxk1VNr1CDrnJy5QDh6XLO6FrLNSRb2l9gz0wo3S6m7HErSgLsPGMHkpDZK31jOanXeHPQz-eruLHUP0z6yTbpbrn223V70uMXNSpQSZjpL0y8hcxxpNqA6_ql3BQAxlxvfpQ_uT9GrWjQC6iRHM8D0MP0GQsIi92QEAAA=):
+
+```svelte
+{#snippet figure(image)}
+
+
+ {image.caption}
+
+{/snippet}
+
+{#each images as image}
+ {#if image.href}
+
+ {@render figure(image)}
+
+ {:else}
+ {@render figure(image)}
+ {/if}
+{/each}
+```
+
+Like 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.
+
+## Snippet scope
+
+Snippets can be declared anywhere inside your component. They can reference values declared outside themselves, for example in the `
+
+{#snippet hello(name)}
+ hello {name}! {message}!
+{/snippet}
+
+{@render hello('alice')}
+{@render hello('bob')}
+```
+
+...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings):
+
+```svelte
+
+ {#snippet x()}
+ {#snippet y()}...{/snippet}
+
+
+ {@render y()}
+ {/snippet}
+
+
+ {@render y()}
+
+
+
+{@render x()}
+```
+
+Snippets can reference themselves and each other ([demo](/playground/untitled#H4sIAAAAAAAAE2WPTQqDMBCFrxLiRqH1Zysi7TlqF1YnENBJSGJLCYGeo5tesUeosfYH3c2bee_jjaWMd6BpfrAU6x5oTvdS0g01V-mFPkNnYNRaDKrxGxto5FKCIaeu1kYwFkauwsoUWtZYPh_3W5FMY4U2mb3egL9kIwY0rbhgiO-sDTgjSEqSTvIDs-jiOP7i_MHuFGAL6p9BtiSbOTl0GtzCuihqE87cqtyam6WRGz_vRcsZh5bmRg3gju4Fptq_kzQBAAA=)):
+
+```svelte
+{#snippet blastoff()}
+ 🚀
+{/snippet}
+
+{#snippet countdown(n)}
+ {#if n > 0}
+ {n}...
+ {@render countdown(n - 1)}
+ {:else}
+ {@render blastoff()}
+ {/if}
+{/snippet}
+
+{@render countdown(10)}
+```
+
+## Passing snippets to components
+
+Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/playground/untitled#H4sIAAAAAAAAE3VS247aMBD9lZGpBGwDASRegonaPvQL2qdlH5zYEKvBNvbQLbL875VzAcKyj3PmzJnLGU8UOwqSkd8KJdaCk4TsZS0cyV49wYuJuQiQpGd-N2bu_ooaI1YwJ57hpVYoFDqSEepKKw3mO7VDeTTaIvxiRS1gb_URxvO0ibrS8WanIrHUyiHs7Vmigy28RmyHHmKvDMbMmFq4cQInvGSwTsBYWYoMVhCSB2rBFFPsyl0uruTlR3JZCWvlTXl1Yy_mawiR_rbZKZrellJ-5JQ0RiBUgnFhJ9OGR7HKmwVoilXeIye8DOJGfYCgRlZ3iE876TBsZPX7hPdteO75PC4QaIo8vwNPePmANQ2fMeEFHrLD7rR1jTNkW986E8C3KwfwVr8HSHOSEBT_kGRozyIkn_zQveXDL3rIfPJHtUDwzShJd_Qk3gQCbOGLsdq4yfTRJopRuin3I7nv6kL7ARRjmLdBDG3uv1mhuLA3V2mKtqNEf_oCn8p9aN-WYqH5peP4kWBl1UwJzAEPT9U7K--0fRrrWnPTXpCm1_EVdXjpNmlA8G1hPPyM1fKgMqjFHjctXGjLhZ05w0qpDhksGrybuNEHtJnCalZWsuaTlfq6nPaaBSv_HKw-K57BjzOiVj9ZKQYKzQjZodYFqydYTRN4gPhVzTDO2xnma3HsVWjaLjT8nbfwHy7Q5f2dBAAA)):
+
+```svelte
+
+
+{#snippet header()}
+ fruit
+ qty
+ price
+ total
+{/snippet}
+
+{#snippet row(d)}
+ {d.name}
+ {d.qty}
+ {d.price}
+ {d.qty * d.price}
+{/snippet}
+
+
+```
+
+Think about it like passing content instead of data to a component. The concept is similar to slots in web components.
+
+As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component ([demo](/playground/untitled#H4sIAAAAAAAAE3VSTa_aMBD8Kyu_SkAbCA-JSzBR20N_QXt6vIMTO8SqsY29tI2s_PcqTiB8vaPHs7MzuxuIZgdBMvJLo0QlOElIJZXwJHsLBBvb_XUASc7Mb9Yu_B-hsMMK5sUzvDQahUZPMkJ96aTFfKd3KA_WOISfrFACKmcOMFmk8TWUTjY73RFLoz1C5U4SPWzhrcN2GKDrlcGEWauEnyRwxCaDdQLWyVJksII2uaMWTDPNLtzX5YX8-kgua-GcHJVXI3u5WEPb0d83O03TMZSmfRzOkG1Db7mNacOL19JagVALxoWbztq-H8U6j0SaYp2P2BGbOyQ2v8PQIFMXLKRDk177pq0zf6d8bMrzwBdd0pamyPMb-IjNEzS2f86Gz_Dwf-2F9nvNSUJQ_EOSoTuJNvngqK5v4Pas7n4-OCwlEEJcQTIMO-nSQwtb-GSdsX46e9gbRoP9yGQ11I0rEuycunu6PHx1QnPhxm3SFN15MOlYEFJZtf0dUywMbwZOeBGsrKNLYB54-1R9WNqVdki7usim6VmQphf7mnpshiQRhNAXdoOfMyX3OgMlKtz0cGEcF27uLSul3mewjPjgOOoDukxjPS9rqfh0pb-8zs6aBSt_7505aZ7B9xOi0T9YKW4UooVsr0zB1BTrWQJ3EL-oWcZ572GxFoezCk37QLe3897-B2i2U62uBAAA)):
+
+```svelte
+
+
+ {#snippet header()}
+ fruit
+ qty
+ price
+ total
+ {/snippet}
+
+ {#snippet row(d)}
+ {d.name}
+ {d.qty}
+ {d.price}
+ {d.qty * d.price}
+ {/snippet}
+
+```
+
+Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet ([demo](/playground/untitled#H4sIAAAAAAAAE3WOQQrCMBBFrzIMggql3ddY1Du4si5sOmIwnYRkFKX07lKqglqX8_7_w2uRDw1hjlsWI5ZqTPBoLEXMdy3K3fdZDzB5Ndfep_FKVnpWHSKNce1YiCVijirqYLwUJQOYxrsgsLmIOIZjcA1M02w4n-PpomSVvTclqyEutDX6DA2pZ7_ABIVugrmEC3XJH92P55_G39GodCmWBFrQJ2PrQAwdLGHig_NxNv9xrQa1dhWIawrv1Wzeqawa8953D-8QOmaEAQAA)):
+
+```svelte
+
+click me
+```
+
+```svelte
+
+
+
+
+{@render children()}
+```
+
+
+You can declare snippet props as being optional. You can either use optional chaining to not render anything if the snippet isn't set...
+
+```svelte
+
+
+{@render children?.()}
+```
+
+...or use an `#if` block to render fallback content:
+
+```svelte
+
+
+{#if children}
+ {@render children()}
+{:else}
+ fallback content
+{/if}
+```
+
+## Typing snippets
+
+Snippets implement the `Snippet` interface imported from `'svelte'`:
+
+```svelte
+
+```
+
+With 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.
+
+We can tighten things up further by declaring a generic, so that `data` and `row` refer to the same type:
+
+```svelte
+
+```
+
+## Exporting snippets
+
+Snippets declared at the top level of a `.svelte` file can be exported from a `
+
+{#snippet add(a, b)}
+ {a} + {b} = {a + b}
+{/snippet}
+```
+
+> This requires Svelte 5.5.0 or newer
+
+## Programmatic snippets
+
+Snippets can be created programmatically with the [`createRawSnippet`](svelte#createRawSnippet) API. This is intended for advanced use cases.
+
+## Snippets and slots
+
+In Svelte 4, content can be passed to components using [slots](legacy-slots). Snippets are more powerful and flexible, and as such slots are deprecated in Svelte 5.
+
+## docs/svelte/03-template-syntax/07-@render.md
+
+---
+
+title: {@render ...}
+---
+
+To render a [snippet](snippet), use a `{@render ...}` tag.
+
+```svelte
+{#snippet sum(a, b)}
+ {a} + {b} = {a + b}
+{/snippet}
+
+{@render sum(1, 2)}
+{@render sum(3, 4)}
+{@render sum(5, 6)}
+```
+
+The expression can be an identifier like `sum`, or an arbitrary JavaScript expression:
+
+```svelte
+{@render (cool ? coolSnippet : lameSnippet)()}
+```
+
+## Optional snippets
+
+If 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:
+
+```svelte
+{@render children?.()}
+```
+
+Alternatively, use an [`{#if ...}`](if) block with an `:else` clause to render fallback content:
+
+```svelte
+{#if children}
+ {@render children()}
+{:else}
+ fallback content
+{/if}
+```
+
+## docs/svelte/03-template-syntax/08-@html.md
+
+---
+
+title: {@html ...}
+---
+
+To inject raw HTML into your component, use the `{@html ...}` tag:
+
+```svelte
+
+ {@html content}
+
+```
+
+
+The expression should be valid standalone HTML — this will not work, because ` ` is not valid HTML:
+
+```svelte
+{@html ''}content{@html '
'}
+```
+
+It also will not compile Svelte code.
+
+## Styling
+
+Content rendered this way is 'invisible' to Svelte and as such will not receive [scoped styles](scoped-styles) — in other words, this will not work, and the `a` and `img` styles will be regarded as unused:
+
+```svelte
+
+ {@html content}
+
+
+
+```
+
+Instead, use the `:global` modifier to target everything inside the ``:
+
+```svelte
+
+```
+
+## docs/svelte/03-template-syntax/09-@const.md
+
+---
+
+title: {@const ...}
+---
+
+The `{@const ...}` tag defines a local constant.
+
+```svelte
+{#each boxes as box}
+ {@const area = box.width * box.height}
+ {box.width} * {box.height} = {area}
+{/each}
+```
+
+`{@const}` is only allowed as an immediate child of a block — `{#if ...}`, `{#each ...}`, `{#snippet ...}` and so on — a ` ` or a ``.
+
+## docs/svelte/03-template-syntax/10-@debug.md
+
+---
+
+title: {@debug ...}
+---
+
+The `{@debug ...}` tag offers an alternative to `console.log(...)`. It logs the values of specific variables whenever they change, and pauses code execution if you have devtools open.
+
+```svelte
+
+
+{@debug user}
+
+Hello {user.firstname}!
+```
+
+`{@debug ...}` accepts a comma-separated list of variable names (not arbitrary expressions).
+
+```svelte
+
+{@debug user}
+{@debug user1, user2, user3}
+
+
+{@debug user.firstname}
+{@debug myArray[0]}
+{@debug !isReady}
+{@debug typeof user === 'object'}
+```
+
+The `{@debug}` tag without any arguments will insert a `debugger` statement that gets triggered when _any_ state changes, as opposed to the specified variables.
+
+## docs/svelte/03-template-syntax/11-bind.md
+
+---
+
+title: bind:
+---
+
+Data ordinarily flows down, from parent to child. The `bind:` directive allows data to flow the other way, from child to parent.
+
+The general syntax is `bind:property={expression}`, where `expression` is an _lvalue_ (i.e. a variable or an object property). When the expression is an identifier with the same name as the property, we can omit the expression — in other words these are equivalent:
+
+```svelte
+
+
+```
+
+
+Svelte creates an event listener that updates the bound value. If an element already has a listener for the same event, that listener will be fired before the bound value is updated.
+
+Most bindings are _two-way_, meaning that changes to the value will affect the element and vice versa. A few bindings are _readonly_, meaning that changing their value will have no effect on the element.
+
+## Function bindings
+
+You can also use `bind:property={get, set}`, where `get` and `set` are functions, allowing you to perform validation and transformation:
+
+```svelte
+ value,
+ (v) => value = v.toLowerCase()}
+/>
+```
+
+In the case of readonly bindings like [dimension bindings](#Dimensions), the `get` value should be `null`:
+
+```svelte
+...
+```
+
+> Function bindings are available in Svelte 5.9.0 and newer.
+
+## ` `
+
+A `bind:value` directive on an ` ` element binds the input's `value` property:
+
+```svelte
+
+
+
+{message}
+```
+
+In the case of a numeric input (`type="number"` or `type="range"`), the value will be coerced to a number ([demo](/playground/untitled#H4sIAAAAAAAAE6WPwYoCMQxAfyWEPeyiOOqx2w74Hds9pBql0IllmhGXYf5dKqwiyILsLXnwwsuI-5i4oPkaUX8yo7kCnKNQV7dNzoty4qSVBSr8jG-Poixa0KAt2z5mbb14TaxA4OCtKCm_rz4-f2m403WltrlrYhMFTtcLNkoeFGqZ8yhDF7j3CCHKzpwoDexGmqCL4jwuPUJHZ-dxVcfmyYGe5MAv-La5pbxYFf5Z9Zf_UJXb-sEMquFgJJhBmGyTW5yj8lnRaD_w9D1dAKSSj7zqAQAA)):
+
+```svelte
+
+
+
+
+
+
+
+
+
+
+
+
+{a} + {b} = {a + b}
+```
+
+If the input is empty or invalid (in the case of `type="number"`), the value is `undefined`.
+
+Since 5.6.0, if an ` ` has a `defaultValue` and is part of a form, it will revert to that value instead of the empty string when the form is reset. Note that for the initial render the value of the binding takes precedence unless it is `null` or `undefined`.
+
+```svelte
+
+
+
+```
+
+> Use reset buttons sparingly, and ensure that users won't accidentally click them while trying to submit the form.
+
+## ` `
+
+Checkbox and radio inputs can be bound with `bind:checked`:
+
+```svelte
+
+
+ Accept terms and conditions
+
+```
+
+Since 5.6.0, if an ` ` has a `defaultChecked` attribute and is part of a form, it will revert to that value instead of `false` when the form is reset. Note that for the initial render the value of the binding takes precedence unless it is `null` or `undefined`.
+
+```svelte
+
+
+
+```
+
+## ` `
+
+Inputs that work together can use `bind:group`.
+
+```svelte
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+## ` `
+
+On ` ` elements with `type="file"`, you can use `bind:files` to get the [`FileList` of selected files](https://developer.mozilla.org/en-US/docs/Web/API/FileList). When you want to update the files programmatically, you always need to use a `FileList` object. Currently `FileList` objects cannot be constructed directly, so you need to create a new [`DataTransfer`](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer) object and get `files` from there.
+
+```svelte
+
+
+Upload a picture:
+
+clear
+```
+
+`FileList` objects also cannot be modified, so if you want to e.g. delete a single file from the list, you need to create a new `DataTransfer` object and add the files you want to keep.
+
+
+## ``
+
+A `` value binding corresponds to the `value` property on the selected ``, which can be any value (not just strings, as is normally the case in the DOM).
+
+```svelte
+
+ a
+ b
+ c
+
+```
+
+A `` element behaves similarly to a checkbox group. The bound variable is an array with an entry corresponding to the `value` property of each selected ``.
+
+```svelte
+
+ Rice
+ Beans
+ Cheese
+ Guac (extra)
+
+```
+
+When the value of an ` ` matches its text content, the attribute can be omitted.
+
+```svelte
+
+ Rice
+ Beans
+ Cheese
+ Guac (extra)
+
+```
+
+You can give the `` a default value by adding a `selected` attribute to the`` (or options, in the case of ``) that should be initially selected. If the `` is part of a form, it will revert to that selection when the form is reset. Note that for the initial render the value of the binding takes precedence if it's not `undefined`.
+
+```svelte
+
+ a
+ b
+ c
+
+```
+
+## ``
+
+`` elements have their own set of bindings — five two-way ones...
+
+- [`currentTime`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/currentTime)
+- [`playbackRate`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/playbackRate)
+- [`paused`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/paused)
+- [`volume`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/volume)
+- [`muted`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/muted)
+
+...and six readonly ones:
+
+- [`duration`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/duration)
+- [`buffered`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/buffered)
+- [`seekable`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seekable)
+- [`seeking`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/seeking_event)
+- [`ended`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/ended)
+- [`readyState`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState)
+
+```svelte
+
+```
+
+## ``
+
+`` elements have all the same bindings as [``](#audio) elements, plus readonly [`videoWidth`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoWidth) and [`videoHeight`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoHeight) bindings.
+
+## ` `
+
+` ` elements have two readonly bindings:
+
+- [`naturalWidth`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/naturalWidth)
+- [`naturalHeight`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/naturalHeight)
+
+## ``
+
+`` elements support binding to the `open` property.
+
+```svelte
+
+ How do you comfort a JavaScript bug?
+ You console it.
+
+```
+
+## Contenteditable bindings
+
+Elements with the `contenteditable` attribute support the following bindings:
+
+- [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML)
+- [`innerText`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText)
+- [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent)
+
+
+
+```svelte
+
+```
+
+## Dimensions
+
+All visible elements have the following readonly bindings, measured with a `ResizeObserver`:
+
+- [`clientWidth`](https://developer.mozilla.org/en-US/docs/Web/API/Element/clientWidth)
+- [`clientHeight`](https://developer.mozilla.org/en-US/docs/Web/API/Element/clientHeight)
+- [`offsetWidth`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetWidth)
+- [`offsetHeight`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetHeight)
+
+```svelte
+
+
+
+```
+
+
+## bind:this
+
+```svelte
+
+bind:this={dom_node}
+```
+
+To get a reference to a DOM node, use `bind:this`. The value will be `undefined` until the component is mounted — in other words, you should read it inside an effect or an event handler, but not during component initialisation:
+
+```svelte
+
+
+
+```
+
+Components also support `bind:this`, allowing you to interact with component instances programmatically.
+
+```svelte
+
+
+
+ cart.empty()}> Empty shopping cart
+```
+
+```svelte
+
+
+```
+
+## bind:_property_ for components
+
+```svelte
+bind:property={variable}
+```
+
+You can bind to component props using the same syntax as for elements.
+
+```svelte
+
+```
+
+While Svelte props are reactive without binding, that reactivity only flows downward into the component by default. Using `bind:property` allows changes to the property from within the component to flow back up out of the component.
+
+To mark a property as bindable, use the [`$bindable`]($bindable) rune:
+
+```svelte
+
+```
+
+Declaring a property as bindable means it _can_ be used using `bind:`, not that it _must_ be used using `bind:`.
+
+Bindable properties can have a fallback value:
+
+```svelte
+
+```
+
+This fallback value _only_ applies when the property is _not_ bound. When the property is bound and a fallback value is present, the parent is expected to provide a value other than `undefined`, else a runtime error is thrown. This prevents hard-to-reason-about situations where it's unclear which value should apply.
+
+## docs/svelte/03-template-syntax/12-use.md
+
+---
+
+title: use:
+---
+
+Actions are functions that are called when an element is mounted. They are added with the `use:` directive, and will typically use an `$effect` so that they can reset any state when the element is unmounted:
+
+```svelte
+
+
+
+...
+```
+
+An action can be called with an argument:
+
+```svelte
+
+
+
+...
+```
+
+The action is only called once (but not during server-side rendering) — it will _not_ run again if the argument changes.
+
+> [!LEGACY]
+> Prior to the `$effect` rune, actions could return an object with `update` and `destroy` methods, where `update` would be called with the latest value of the argument if it changed. Using effects is preferred.
+
+## Typing
+
+The `Action` interface receives three optional type arguments — a node type (which can be `Element`, if the action applies to everything), a parameter, and any custom event handlers created by the action:
+
+```svelte
+
+
+
+...
+```
+
+## docs/svelte/03-template-syntax/13-transition.md
+
+---
+
+title: transition:
+---
+
+A _transition_ is triggered by an element entering or leaving the DOM as a result of a state change.
+
+When a block (such as an `{#if ...}` block) is transitioning out, all elements inside it, including those that do not have their own transitions, are kept in the DOM until every transition in the block has been completed.
+
+The `transition:` directive indicates a _bidirectional_ transition, which means it can be smoothly reversed while the transition is in progress.
+
+```svelte
+
+
+ visible = !visible}>toggle
+
+{#if visible}
+ fades in and out
+{/if}
+```
+
+## Built-in transitions
+
+A selection of built-in transitions can be imported from the [`svelte/transition`](svelte-transition) module.
+
+## Local vs global
+
+Transitions are local by default. Local transitions only play when the block they belong to is created or destroyed, _not_ when parent blocks are created or destroyed.
+
+```svelte
+{#if x}
+ {#if y}
+ fades in and out only when y changes
+
+ fades in and out when x or y change
+ {/if}
+{/if}
+```
+
+## Transition parameters
+
+Transitions can have parameters.
+
+(The double `{{curlies}}` aren't a special syntax; this is an object literal inside an expression tag.)
+
+```svelte
+{#if visible}
+ fades in and out over two seconds
+{/if}
+```
+
+## Custom transition functions
+
+```js
+/// copy: false
+// @noErrors
+transition = (node: HTMLElement, params: any, options: { direction: 'in' | 'out' | 'both' }) => {
+ delay?: number,
+ duration?: number,
+ easing?: (t: number) => number,
+ css?: (t: number, u: number) => string,
+ tick?: (t: number, u: number) => void
+}
+```
+
+Transitions can use custom functions. If the returned object has a `css` function, Svelte will generate keyframes for a [web animation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).
+
+The `t` argument passed to `css` is a value between `0` and `1` after the `easing` function has been applied. _In_ transitions run from `0` to `1`, _out_ transitions run from `1` to `0` — in other words, `1` is the element's natural state, as though no transition had been applied. The `u` argument is equal to `1 - t`.
+
+The function is called repeatedly _before_ the transition begins, with different `t` and `u` arguments.
+
+```svelte
+
+
+
+{#if visible}
+ whooshes in
+{/if}
+```
+
+A custom transition function can also return a `tick` function, which is called _during_ the transition with the same `t` and `u` arguments.
+
+
+```svelte
+
+
+
+{#if visible}
+ The quick brown fox jumps over the lazy dog
+{/if}
+```
+
+If a transition returns a function instead of a transition object, the function will be called in the next microtask. This allows multiple transitions to coordinate, making [crossfade effects](/tutorial/deferred-transitions) possible.
+
+Transition functions also receive a third argument, `options`, which contains information about the transition.
+
+Available values in the `options` object are:
+
+- `direction` - one of `in`, `out`, or `both` depending on the type of transition
+
+## Transition events
+
+An element with transitions will dispatch the following events in addition to any standard DOM events:
+
+- `introstart`
+- `introend`
+- `outrostart`
+- `outroend`
+
+```svelte
+{#if visible}
+ (status = 'intro started')}
+ onoutrostart={() => (status = 'outro started')}
+ onintroend={() => (status = 'intro ended')}
+ onoutroend={() => (status = 'outro ended')}
+ >
+ Flies in and out
+
+{/if}
+```
+
+## docs/svelte/03-template-syntax/14-in-and-out.md
+
+---
+
+title: in: and out:
+---
+
+The `in:` and `out:` directives are identical to [`transition:`](transition), except that the resulting transitions are not bidirectional — an `in` transition will continue to 'play' alongside the `out` transition, rather than reversing, if the block is outroed while the transition is in progress. If an out transition is aborted, transitions will restart from scratch.
+
+```svelte
+
+
+
+
+ visible
+
+
+{#if visible}
+ flies in, fades out
+{/if}
+```
+
+## docs/svelte/03-template-syntax/15-animate.md
+
+---
+
+title: animate:
+---
+
+An animation is triggered when the contents of a [keyed each block](each#Keyed-each-blocks) are re-ordered. Animations do not run when an element is added or removed, only when the index of an existing data item within the each block changes. Animate directives must be on an element that is an _immediate_ child of a keyed each block.
+
+Animations can be used with Svelte's [built-in animation functions](svelte-animate) or [custom animation functions](#Custom-animation-functions).
+
+```svelte
+
+{#each list as item, index (item)}
+ {item}
+{/each}
+```
+
+## Animation Parameters
+
+As with actions and transitions, animations can have parameters.
+
+(The double `{{curlies}}` aren't a special syntax; this is an object literal inside an expression tag.)
+
+```svelte
+{#each list as item, index (item)}
+ {item}
+{/each}
+```
+
+## Custom animation functions
+
+```js
+/// copy: false
+// @noErrors
+animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) => {
+ delay?: number,
+ duration?: number,
+ easing?: (t: number) => number,
+ css?: (t: number, u: number) => string,
+ tick?: (t: number, u: number) => void
+}
+```
+
+Animations can use custom functions that provide the `node`, an `animation` object and any `parameters` as arguments. The `animation` parameter is an object containing `from` and `to` properties each containing a [DOMRect](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect#Properties) describing the geometry of the element in its `start` and `end` positions. The `from` property is the DOMRect of the element in its starting position, and the `to` property is the DOMRect of the element in its final position after the list has been reordered and the DOM updated.
+
+If the returned object has a `css` method, Svelte will create a [web animation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) that plays on the element.
+
+The `t` argument passed to `css` is a value that goes from `0` and `1` after the `easing` function has been applied. The `u` argument is equal to `1 - t`.
+
+The function is called repeatedly _before_ the animation begins, with different `t` and `u` arguments.
+
+
+
+```svelte
+
+
+
+{#each list as item, index (item)}
+ {item}
+{/each}
+```
+
+A custom animation function can also return a `tick` function, which is called _during_ the animation with the same `t` and `u` arguments.
+
+
+```svelte
+
+
+
+{#each list as item, index (item)}
+ {item}
+{/each}
+```
+
+## docs/svelte/03-template-syntax/17-style.md
+
+---
+
+title: style:
+---
+
+The `style:` directive provides a shorthand for setting multiple styles on an element.
+
+```svelte
+
+...
+...
+```
+
+The value can contain arbitrary expressions:
+
+```svelte
+...
+```
+
+The shorthand form is allowed:
+
+```svelte
+...
+```
+
+Multiple styles can be set on a single element:
+
+```svelte
+...
+```
+
+To mark a style as important, use the `|important` modifier:
+
+```svelte
+...
+```
+
+When `style:` directives are combined with `style` attributes, the directives will take precedence:
+
+```svelte
+This will be red
+```
+
+## docs/svelte/03-template-syntax/18-class.md
+
+---
+
+title: class
+---
+
+There are two ways to set classes on elements: the `class` attribute, and the `class:` directive.
+
+## Attributes
+
+Primitive values are treated like any other attribute:
+
+```svelte
+...
+```
+
+> For historical reasons, falsy values (like `false` and `NaN`) are stringified (`class="false"`), though `class={undefined}` (or `null`) cause the attribute to be omitted altogether. In a future version of Svelte, all falsy values will cause `class` to be omitted.
+
+### Objects and arrays
+
+Since Svelte 5.16, `class` can be an object or array, and is converted to a string using [clsx](https://github.com/lukeed/clsx).
+
+If the value is an object, the truthy keys are added:
+
+```svelte
+
+
+
+...
+```
+
+If the value is an array, the truthy values are combined:
+
+```svelte
+
+...
+```
+
+Note that whether we're using the array or object form, we can set multiple classes simultaneously with a single condition, which is particularly useful if you're using things like Tailwind.
+
+Arrays can contain arrays and objects, and clsx will flatten them. This is useful for combining local classes with props, for example:
+
+```svelte
+
+
+
+
+ {@render props.children?.()}
+
+```
+
+The user of this component has the same flexibility to use a mixture of objects, arrays and strings:
+
+```svelte
+
+
+
+ useTailwind = true}
+ class={{ 'bg-blue-700 sm:w-1/2': useTailwind }}
+>
+ Accept the inevitability of Tailwind
+
+```
+
+Svelte also exposes the `ClassValue` type, which is the type of value that the `class` attribute on elements accept. This is useful if you want to use a type-safe class name in component props:
+
+```svelte
+
+
+...
+```
+
+## The `class:` directive
+
+Prior to Svelte 5.16, the `class:` directive was the most convenient way to set classes on elements conditionally.
+
+```svelte
+
+...
+...
+```
+
+As with other directives, we can use a shorthand when the name of the class coincides with the value:
+
+```svelte
+...
+```
+
+## docs/svelte/03-template-syntax/xx-control-flow.md
+
+---
+
+title: Control flow
+---
+
+- if
+- each
+- await (or move that into some kind of data loading section?)
+- NOT: key (move into transition section, because that's the common use case)
+
+Svelte augments HTML with control flow blocks to be able to express conditionally rendered content or lists.
+
+The syntax between these blocks is the same:
+
+- `{#` denotes the start of a block
+- `{:` denotes a different branch part of the block. Depending on the block, there can be multiple of these
+- `{/` denotes the end of a block
+
+## {#if ...}
+
+## {#each ...}
+
+```svelte
+
+{#each expression as name}...{/each}
+```
+
+```svelte
+
+{#each expression as name, index}...{/each}
+```
+
+```svelte
+
+{#each expression as name (key)}...{/each}
+```
+
+```svelte
+
+{#each expression as name, index (key)}...{/each}
+```
+
+```svelte
+
+{#each expression as name}...{:else}...{/each}
+```
+
+Iterating over lists of values can be done with an each block.
+
+```svelte
+Shopping list
+
+ {#each items as item}
+ {item.name} x {item.qty}
+ {/each}
+
+```
+
+You can use each blocks to iterate over any array or array-like value — that is, any object with a `length` property.
+
+An each block can also specify an _index_, equivalent to the second argument in an `array.map(...)` callback:
+
+```svelte
+{#each items as item, i}
+ {i + 1}: {item.name} x {item.qty}
+{/each}
+```
+
+If a _key_ expression is provided — which must uniquely identify each list item — Svelte will use it to diff the list when data changes, rather than adding or removing items at the end. The key can be any object, but strings and numbers are recommended since they allow identity to persist when the objects themselves change.
+
+```svelte
+{#each items as item (item.id)}
+ {item.name} x {item.qty}
+{/each}
+
+
+{#each items as item, i (item.id)}
+ {i + 1}: {item.name} x {item.qty}
+{/each}
+```
+
+You can freely use destructuring and rest patterns in each blocks.
+
+```svelte
+{#each items as { id, name, qty }, i (id)}
+ {i + 1}: {name} x {qty}
+{/each}
+
+{#each objects as { id, ...rest }}
+ {id}
+{/each}
+
+{#each items as [id, ...rest]}
+ {id}
+{/each}
+```
+
+An each block can also have an `{:else}` clause, which is rendered if the list is empty.
+
+```svelte
+{#each todos as todo}
+ {todo.text}
+{:else}
+ No tasks today!
+{/each}
+```
+
+It is possible to iterate over iterables like `Map` or `Set`. Iterables need to be finite and static (they shouldn't change while being iterated over). Under the hood, they are transformed to an array using `Array.from` before being passed off to rendering. If you're writing performance-sensitive code, try to avoid iterables and use regular arrays as they are more performant.
+
+## Other block types
+
+Svelte also provides [`#snippet`](snippets), [`#key`](transitions-and-animations) and [`#await`](data-fetching) blocks. You can find out more about them in their respective sections.
+
+## docs/svelte/03-template-syntax/xx-data-fetching.md
+
+---
+
+title: Data fetching
+---
+
+Fetching data is a fundamental part of apps interacting with the outside world. Svelte is unopinionated with how you fetch your data. The simplest way would be using the built-in `fetch` method:
+
+```svelte
+
+```
+
+While this works, it makes working with promises somewhat unergonomic. Svelte alleviates this problem using the `#await` block.
+
+## {#await ...}
+
+## SvelteKit loaders
+
+Fetching inside your components is great for simple use cases, but it's prone to data loading waterfalls and makes code harder to work with because of the promise handling. SvelteKit solves this problem by providing a opinionated data loading story that is coupled to its router. Learn more about it [in the docs](../kit).
+
+## docs/svelte/04-styling/index.md
+
+---
+
+title: Styling
+---
+
+## docs/svelte/04-styling/01-scoped-styles.md
+
+---
+
+title: Scoped styles
+---
+
+Svelte components can include a `
+```
+
+## Specificity
+
+Each scoped selector receives a [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) increase of 0-1-0, as a result of the scoping class (e.g. `.svelte-123xyz`) being added to the selector. This means that (for example) a `p` selector defined in a component will take precedence over a `p` selector defined in a global stylesheet, even if the global stylesheet is loaded later.
+
+In some cases, the scoping class must be added to a selector multiple times, but after the first occurrence it is added with `:where(.svelte-xyz123)` in order to not increase specificity further.
+
+## Scoped keyframes
+
+If a component defines `@keyframes`, the name is scoped to the component using the same hashing approach. Any `animation` rules in the component will be similarly adjusted:
+
+```svelte
+
+```
+
+## docs/svelte/04-styling/02-global-styles.md
+
+---
+
+title: Global styles
+---
+
+## :global(...)
+
+To apply styles to a single selector globally, use the `:global(...)` modifier:
+
+```svelte
+
+```
+
+If you want to make @keyframes that are accessible globally, you need to prepend your keyframe names with `-global-`.
+
+The `-global-` part will be removed when compiled, and the keyframe will then be referenced using just `my-animation-name` elsewhere in your code.
+
+```svelte
+
+```
+
+## :global
+
+To apply styles to a group of selectors globally, create a `:global {...}` block:
+
+```svelte
+
+```
+
+## docs/svelte/04-styling/03-custom-properties.md
+
+---
+
+title: Custom properties
+---
+
+You can pass CSS custom properties — both static and dynamic — to components:
+
+```svelte
+
+```
+
+The above code essentially desugars to this:
+
+```svelte
+
+
+
+```
+
+For an SVG element, it would use `` instead:
+
+```svelte
+
+
+
+```
+
+Inside the component, we can read these custom properties (and provide fallback values) using [`var(...)`](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties):
+
+```svelte
+
+```
+
+You don't _have_ to specify the values directly on the component; as long as the custom properties are defined on a parent element, the component can use them. It's common to define custom properties on the `:root` element in a global stylesheet so that they apply to your entire application.
+
+## docs/svelte/04-styling/04-nested-style-elements.md
+
+---
+
+title: Nested
+
+```
+
+## docs/svelte/05-special-elements/index.md
+
+---
+
+title: Special elements
+---
+
+## docs/svelte/05-special-elements/01-svelte-boundary.md
+
+---
+
+title:
+---
+
+```svelte
+...
+```
+
+> This feature was added in 5.3.0
+
+Boundaries allow you to guard against errors in part of your app from breaking the app as a whole, and to recover from those errors.
+
+If an error occurs while rendering or updating the children of a ``, or running any [`$effect`]($effect) functions contained therein, the contents will be removed.
+
+Errors occurring outside the rendering process (for example, in event handlers or after a `setTimeout` or async work) are _not_ caught by error boundaries.
+
+## Properties
+
+For the boundary to do anything, one or both of `failed` and `onerror` must be provided.
+
+### `failed`
+
+If a `failed` snippet is provided, it will be rendered with the error that was thrown, and a `reset` function that recreates the contents ([demo](/playground/hello-world#H4sIAAAAAAAAE3VRy26DMBD8lS2tFCIh6JkAUlWp39Cq9EBg06CAbdlLArL87zWGKk8ORnhmd3ZnrD1WtOjFXqKO2BDGW96xqpBD5gXerm5QefG39mgQY9EIWHxueRMinLosti0UPsJLzggZKTeilLWgLGc51a3gkuCjKQ7DO7cXZotgJ3kLqzC6hmex1SZnSXTWYHcrj8LJjWTk0PHoZ8VqIdCOKayPykcpuQxAokJaG1dGybYj4gw4K5u6PKTasSbjXKgnIDlA8VvUdo-pzonraBY2bsH7HAl78mKSHZpgIcuHjq9jXSpZSLixRlveKYQUXhQVhL6GPobXAAb7BbNeyvNUs4qfRg3OnELLj5hqH9eQZqCnoBwR9lYcQxuVXeBzc8kMF8yXY4yNJ5oGiUzP_aaf_waTRGJib5_Ad3P_vbCuaYxzeNpbU0eUMPAOKh7Yw1YErgtoXyuYlPLzc10_xo_5A91zkQL_AgAA)):
+
+```svelte
+
+
+
+ {#snippet failed(error, reset)}
+ oops! try again
+ {/snippet}
+
+```
+
+> As with [snippets passed to components](snippet#Passing-snippets-to-components), the `failed` snippet can be passed explicitly as a property...
+>
+> ```svelte
+> ...
+> ```
+>
+> ...or implicitly by declaring it directly inside the boundary, as in the example above.
+
+### `onerror`
+
+If an `onerror` function is provided, it will be called with the same two `error` and `reset` arguments. This is useful for tracking the error with an error reporting service...
+
+```svelte
+ report(e)}>
+ ...
+
+```
+
+...or using `error` and `reset` outside the boundary itself:
+
+```svelte
+
+
+
+
+
+
+{#if error}
+ {
+ error = null;
+ reset();
+ }}>
+ oops! try again
+
+{/if}
+```
+
+If an error occurs inside the `onerror` function (or if you rethrow the error), it will be handled by a parent boundary if such exists.
+
+## docs/svelte/05-special-elements/02-svelte-window.md
+
+---
+
+title:
+---
+
+```svelte
+
+```
+
+```svelte
+
+```
+
+The `` element allows you to add event listeners to the `window` object without worrying about removing them when the component is destroyed, or checking for the existence of `window` when server-side rendering.
+
+This element may only appear at the top level of your component — it cannot be inside a block or element.
+
+```svelte
+
+
+
+```
+
+You can also bind to the following properties:
+
+- `innerWidth`
+- `innerHeight`
+- `outerWidth`
+- `outerHeight`
+- `scrollX`
+- `scrollY`
+- `online` — an alias for `window.navigator.onLine`
+- `devicePixelRatio`
+
+All except `scrollX` and `scrollY` are readonly.
+
+```svelte
+
+```
+
+## docs/svelte/05-special-elements/03-svelte-document.md
+
+---
+
+title:
+---
+
+```svelte
+
+```
+
+```svelte
+
+```
+
+Similarly to ``, this element allows you to add listeners to events on `document`, such as `visibilitychange`, which don't fire on `window`. It also lets you use [actions](use) on `document`.
+
+As with ``, this element may only appear the top level of your component and must never be inside a block or element.
+
+```svelte
+
+```
+
+You can also bind to the following properties:
+
+- `activeElement`
+- `fullscreenElement`
+- `pointerLockElement`
+- `visibilityState`
+
+All are readonly.
+
+## docs/svelte/05-special-elements/04-svelte-body.md
+
+---
+
+title:
+---
+
+```svelte
+
+```
+
+Similarly to ``, this element allows you to add listeners to events on `document.body`, such as `mouseenter` and `mouseleave`, which don't fire on `window`. It also lets you use [actions](use) on the `` element.
+
+As with `` and ``, this element may only appear the top level of your component and must never be inside a block or element.
+
+```svelte
+
+```
+
+## docs/svelte/05-special-elements/05-svelte-head.md
+
+---
+
+title:
+---
+
+```svelte
+...
+```
+
+This element makes it possible to insert elements into `document.head`. During server-side rendering, `head` content is exposed separately to the main `body` content.
+
+As with ``, `` and ``, this element may only appear at the top level of your component and must never be inside a block or element.
+
+```svelte
+
+ Hello world!
+
+
+```
+
+## docs/svelte/05-special-elements/06-svelte-element.md
+
+---
+
+title:
+---
+
+```svelte
+
+```
+
+The `` element lets you render an element that is unknown at author time, for example because it comes from a CMS. Any properties and event listeners present will be applied to the element.
+
+The only supported binding is `bind:this`, since Svelte's built-in bindings do not work with generic elements.
+
+If `this` has a nullish value, the element and its children will not be rendered.
+
+If `this` is the name of a [void element](https://developer.mozilla.org/en-US/docs/Glossary/Void_element) (e.g., `br`) and `` has child elements, a runtime error will be thrown in development mode:
+
+```svelte
+
+
+
+ This text cannot appear inside an hr element
+
+```
+
+Svelte tries its best to infer the correct namespace from the element's surroundings, but it's not always possible. You can make it explicit with an `xmlns` attribute:
+
+```svelte
+
+```
+
+`this` needs to be a valid DOM element tag, things like `#text` or `svelte:head` will not work.
+
+## docs/svelte/05-special-elements/07-svelte-options.md
+
+---
+
+title:
+---
+
+```svelte
+
+```
+
+The `` element provides a place to specify per-component compiler options, which are detailed in the [compiler section](svelte-compiler#compile). The possible options are:
+
+- `runes={true}` — forces a component into _runes mode_ (see the [Legacy APIs](legacy-overview) section)
+- `runes={false}` — forces a component into _legacy mode_
+- `namespace="..."` — the namespace where this component will be used, can be "html" (the default), "svg" or "mathml"
+- `customElement={...}` — the [options](custom-elements#Component-options) to use when compiling this component as a custom element. If a string is passed, it is used as the `tag` option
+- `css="injected"` — the component will inject its styles inline: During server side rendering, it's injected as a `
+```
+
+### element_invalid_self_closing_tag
+
+```
+Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...>%name%>` rather than `<%name% ... />`
+```
+
+In HTML, there's [no such thing as a self-closing tag](https://jakearchibald.com/2023/against-self-closing-tags-in-html/). While this _looks_ like a self-contained element with some text next to it...
+
+```html
+
+ some text!
+
+```
+
+...a spec-compliant HTML parser (such as a browser) will in fact parse it like this, with the text _inside_ the icon:
+
+```html
+
+ some text!
+
+```
+
+Some templating languages (including Svelte) will 'fix' HTML by turning ` ` into ` `. Others adhere to the spec. Both result in ambiguity and confusion when copy-pasting code between different contexts, and as such Svelte prompts you to resolve the ambiguity directly by having an explicit closing tag.
+
+To automate this, run the dedicated migration:
+
+```bash
+npx sv migrate self-closing-tags
+```
+
+In a future version of Svelte, self-closing tags may be upgraded from a warning to an error.
+
+### event_directive_deprecated
+
+```
+Using `on:%name%` to listen to the %name% event is deprecated. Use the event attribute `on%name%` instead
+```
+
+See [the migration guide](v5-migration-guide#Event-changes) for more info.
+
+### export_let_unused
+
+```
+Component has unused export property '%name%'. If it is for external reference only, please consider using `export const %name%`
+```
+
+### legacy_code
+
+```
+`%code%` is no longer valid — please use `%suggestion%` instead
+```
+
+### legacy_component_creation
+
+```
+Svelte 5 components are no longer classes. Instantiate them using `mount` or `hydrate` (imported from 'svelte') instead.
+```
+
+See the [migration guide](v5-migration-guide#Components-are-no-longer-classes) for more info.
+
+### node_invalid_placement_ssr
+
+```
+%message%. When rendering this component on the server, the resulting HTML will be modified by the browser (by moving, removing, or inserting elements), likely resulting in a `hydration_mismatch` warning
+```
+
+HTML restricts where certain elements can appear. In case of a violation the browser will 'repair' the HTML in a way that breaks Svelte's assumptions about the structure of your components. Some examples:
+
+- `hello
world
` will result in `hello
world
` (the `` autoclosed the `
` because `
` cannot contain block-level elements)
+- `option a
` will result in `option a ` (the `
` is removed)
+- `
` will result in `
` (a `
` is auto-inserted)
+
+This code will work when the component is rendered on the client (which is why this is a warning rather than an error), but if you use server rendering it will cause hydration to fail.
+
+### non_reactive_update
+
+```
+`%name%` is updated, but is not declared with `$state(...)`. Changing its value will not correctly trigger updates
+```
+
+This warning is thrown when the compiler detects the following:
+- a variable was declared without `$state` or `$state.raw`
+- the variable is reassigned
+- the variable is read in a reactive context
+
+In this case, changing the value will not correctly trigger updates. Example:
+
+```svelte
+
+
+This value updates: {reactive}
+This value does not update: {stale}
+
+ {
+ stale = 'updated';
+ reactive = 'updated';
+}}>update
+```
+
+To fix this, wrap your variable declaration with `$state`.
+
+### options_deprecated_accessors
+
+```
+The `accessors` option has been deprecated. It will have no effect in runes mode
+```
+
+### options_deprecated_immutable
+
+```
+The `immutable` option has been deprecated. It will have no effect in runes mode
+```
+
+### options_missing_custom_element
+
+```
+The `customElement` option is used when generating a custom element. Did you forget the `customElement: true` compile option?
+```
+
+### options_removed_enable_sourcemap
+
+```
+The `enableSourcemap` option has been removed. Source maps are always generated now, and tooling can choose to ignore them
+```
+
+### options_removed_hydratable
+
+```
+The `hydratable` option has been removed. Svelte components are always hydratable now
+```
+
+### options_removed_loop_guard_timeout
+
+```
+The `loopGuardTimeout` option has been removed
+```
+
+### options_renamed_ssr_dom
+
+```
+`generate: "dom"` and `generate: "ssr"` options have been renamed to "client" and "server" respectively
+```
+
+### perf_avoid_inline_class
+
+```
+Avoid 'new class' — instead, declare the class at the top level scope
+```
+
+### perf_avoid_nested_class
+
+```
+Avoid declaring classes below the top level scope
+```
+
+### reactive_declaration_invalid_placement
+
+```
+Reactive declarations only exist at the top level of the instance script
+```
+
+### reactive_declaration_module_script_dependency
+
+```
+Reassignments of module-level declarations will not cause reactive statements to update
+```
+
+### script_context_deprecated
+
+```
+`context="module"` is deprecated, use the `module` attribute instead
+```
+
+```svelte
+
+ let foo = 'bar';
+
+```
+
+### script_unknown_attribute
+
+```
+Unrecognized attribute — should be one of `generics`, `lang` or `module`. If this exists for a preprocessor, ensure that the preprocessor removes it
+```
+
+### slot_element_deprecated
+
+```
+Using `` to render parent content is deprecated. Use `{@render ...}` tags instead
+```
+
+See [the migration guide](v5-migration-guide#Snippets-instead-of-slots) for more info.
+
+### state_referenced_locally
+
+```
+State referenced in its own scope will never update. Did you mean to reference it inside a closure?
+```
+
+This warning is thrown when the compiler detects the following:
+- A reactive variable is declared
+- the variable is reassigned
+- the variable is referenced inside the same scope it is declared and it is a non-reactive context
+
+In this case, the state reassignment will not be noticed by whatever you passed it to. For example, if you pass the state to a function, that function will not notice the updates:
+
+```svelte
+
+
+
+ count++}>
+ increment
+
+```
+
+```svelte
+
+
+
+
+The count is {count}
+```
+
+To fix this, reference the variable such that it is lazily evaluated. For the above example, this can be achieved by wrapping `count` in a function:
+
+```svelte
+
+
+
+ count++}>
+ increment
+
+```
+
+```svelte
+
+
+
+
+The count is {count()}
+```
+
+For more info, see [Passing state into functions]($state#Passing-state-into-functions).
+
+### store_rune_conflict
+
+```
+It looks like you're using the `$%name%` rune, but there is a local binding called `%name%`. Referencing a local variable with a `$` prefix will create a store subscription. Please rename `%name%` to avoid the ambiguity
+```
+
+### svelte_component_deprecated
+
+```
+`` is deprecated in runes mode — components are dynamic by default
+```
+
+In previous versions of Svelte, the component constructor was fixed when the component was rendered. In other words, if you wanted `` to re-render when `X` changed, you would either have to use `` or put the component inside a `{#key X}...{/key}` block.
+
+In Svelte 5 this is no longer true — if `X` changes, `` re-renders.
+
+In some cases `` syntax can be used as a replacement; a lowercased variable with property access is recognized as a component in Svelte 5.
+
+For complex component resolution logic, an intermediary, capitalized variable may be necessary. E.g. in places where `@const` can be used:
+
+```svelte
+{#each items as item}
+
+ {@const Component = item.condition ? Y : Z}
+
+{/each}
+```
+
+A derived value may be used in other contexts:
+
+```svelte
+
+
+
+
+```
+
+### svelte_element_invalid_this
+
+```
+`this` should be an `{expression}`. Using a string attribute value will cause an error in future versions of Svelte
+```
+
+### svelte_self_deprecated
+
+```
+`` is deprecated — use self-imports (e.g. `import %name% from './%basename%'`) instead
+```
+
+See [the note in the docs](legacy-svelte-self) for more info.
+
+### unknown_code
+
+```
+`%code%` is not a recognised code
+```
+
+```
+`%code%` is not a recognised code (did you mean `%suggestion%`?)
+```
+
+## docs/svelte/98-reference/30-runtime-errors.md
+
+---
+
+title: 'Runtime errors'
+---
+
+## Client errors
+
+
+
+### bind_invalid_checkbox_value
+
+```
+Using `bind:value` together with a checkbox input is not allowed. Use `bind:checked` instead
+```
+
+### bind_invalid_export
+
+```
+Component %component% has an export named `%key%` that a consumer component is trying to access using `bind:%key%`, which is disallowed. Instead, use `bind:this` (e.g. `<%name% bind:this={component} />`) and then access the property on the bound component instance (e.g. `component.%key%`)
+```
+
+### bind_not_bindable
+
+```
+A component is attempting to bind to a non-bindable property `%key%` belonging to %component% (i.e. `<%name% bind:%key%={...}>`). To mark a property as bindable: `let { %key% = $bindable() } = $props()`
+```
+
+### component_api_changed
+
+```
+%parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5
+```
+
+See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information.
+
+### component_api_invalid_new
+
+```
+Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `compatibility.componentApi` compiler option to `4` to keep it working.
+```
+
+See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information.
+
+### derived_references_self
+
+```
+A derived value cannot reference itself recursively
+```
+
+### each_key_duplicate
+
+```
+Keyed each block has duplicate key at indexes %a% and %b%
+```
+
+```
+Keyed each block has duplicate key `%value%` at indexes %a% and %b%
+```
+
+### effect_in_teardown
+
+```
+`%rune%` cannot be used inside an effect cleanup function
+```
+
+### effect_in_unowned_derived
+
+```
+Effect cannot be created inside a `$derived` value that was not itself created inside an effect
+```
+
+### effect_orphan
+
+```
+`%rune%` can only be used inside an effect (e.g. during component initialisation)
+```
+
+### effect_update_depth_exceeded
+
+```
+Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops
+```
+
+### hydration_failed
+
+```
+Failed to hydrate the application
+```
+
+### invalid_snippet
+
+```
+Could not `{@render}` snippet due to the expression being `null` or `undefined`. Consider using optional chaining `{@render snippet?.()}`
+```
+
+### lifecycle_legacy_only
+
+```
+`%name%(...)` cannot be used in runes mode
+```
+
+### props_invalid_value
+
+```
+Cannot do `bind:%key%={undefined}` when `%key%` has a fallback value
+```
+
+### props_rest_readonly
+
+```
+Rest element properties of `$props()` such as `%property%` are readonly
+```
+
+### rune_outside_svelte
+
+```
+The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files
+```
+
+### state_descriptors_fixed
+
+```
+Property descriptors defined on `$state` objects must contain `value` and always be `enumerable`, `configurable` and `writable`.
+```
+
+### state_prototype_fixed
+
+```
+Cannot set prototype of `$state` object
+```
+
+### state_unsafe_local_read
+
+```
+Reading state that was created inside the same derived is forbidden. Consider using `untrack` to read locally created state
+```
+
+### state_unsafe_mutation
+
+```
+Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state`
+```
+
+
+## Server errors
+
+
+
+### lifecycle_function_unavailable
+
+```
+`%name%(...)` is not available on the server
+```
+
+Certain methods such as `mount` cannot be invoked while running in a server context. Avoid calling them eagerly, i.e. not during render.
+
+
+## Shared errors
+
+
+
+### invalid_default_snippet
+
+```
+Cannot use `{@render children(...)}` if the parent component uses `let:` directives. Consider using a named snippet instead
+```
+
+This error would be thrown in a setup like this:
+
+```svelte
+
+
+ {entry}
+
+```
+
+```svelte
+
+
+
+
+ {#each items as item}
+ {@render children(item)}
+ {/each}
+
+```
+
+Here, `List.svelte` is using `{@render children(item)` which means it expects `Parent.svelte` to use snippets. Instead, `Parent.svelte` uses the deprecated `let:` directive. This combination of APIs is incompatible, hence the error.
+
+### lifecycle_outside_component
+
+```
+`%name%(...)` can only be used during component initialisation
+```
+
+Certain lifecycle methods can only be used during component initialisation. To fix this, make sure you're invoking the method inside the _top level of the instance script_ of your component.
+
+```svelte
+
+
+click me
+```
+
+### store_invalid_shape
+
+```
+`%name%` is not a store with a `subscribe` method
+```
+
+### svelte_element_invalid_this_value
+
+```
+The `this` prop on `` must be a string, if defined
+```
+
+## docs/svelte/98-reference/30-runtime-warnings.md
+
+---
+
+title: 'Runtime warnings'
+---
+
+## Client warnings
+
+
+
+### assignment_value_stale
+
+```
+Assignment to `%property%` property (%location%) will evaluate to the right-hand side, not the value of `%property%` following the assignment. This may result in unexpected behaviour.
+```
+
+Given a case like this...
+
+```svelte
+
+
+add
+items: {JSON.stringify(object.items)}
+```
+
+...the array being pushed to when the button is first clicked is the `[]` on the right-hand side of the assignment, but the resulting value of `object.array` is an empty state proxy. As a result, the pushed value will be discarded.
+
+You can fix this by separating it into two statements:
+
+```js
+let object = { array: [0] };
+//cut
+function add() {
+ object.array ??= [];
+ object.array.push(object.array.length);
+}
+```
+
+### binding_property_non_reactive
+
+```
+`%binding%` is binding to a non-reactive property
+```
+
+```
+`%binding%` (%location%) is binding to a non-reactive property
+```
+
+### console_log_state
+
+```
+Your `console.%method%` contained `$state` proxies. Consider using `$inspect(...)` or `$state.snapshot(...)` instead
+```
+
+When logging a [proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), browser devtools will log the proxy itself rather than the value it represents. In the case of Svelte, the 'target' of a `$state` proxy might not resemble its current value, which can be confusing.
+
+The easiest way to log a value as it changes over time is to use the [`$inspect`](/docs/svelte/$inspect) rune. Alternatively, to log things on a one-off basis (for example, inside an event handler) you can use [`$state.snapshot`](/docs/svelte/$state#$state.snapshot) to take a snapshot of the current value.
+
+### event_handler_invalid
+
+```
+%handler% should be a function. Did you mean to %suggestion%?
+```
+
+### hydration_attribute_changed
+
+```
+The `%attribute%` attribute on `%html%` changed its value between server and client renders. The client value, `%value%`, will be ignored in favour of the server value
+```
+
+Certain attributes like `src` on an ` ` element will not be repaired during hydration, i.e. the server value will be kept. That's because updating these attributes can cause the image to be refetched (or in the case of an `