Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Guidelines:

## SvelteKit configuration

The app uses Svelte's experimental async features and SvelteKit's remote functions. All SvelteKit configuration is passed to the `sveltekit()` plugin in `vite.config.ts` (the newer configuration style, expected to become the default in SvelteKit 3); `svelte.config.js` stays an empty stub so tools that require its presence keep working. The "svelte.config.js is ignored" warning printed by dev/build/check commands is expected with this setup.
The app targets the SvelteKit 3 prerelease line, beginning with `@sveltejs/kit@3.0.0-next.12`. All SvelteKit configuration is passed to the `sveltekit()` plugin in `vite.config.ts`; SvelteKit 3 no longer supports `svelte.config.js`. Internal library imports use the package-level `#lib` subpath alias declared in `package.json`, replacing SvelteKit 2's generated `$lib` alias. The app uses Svelte's experimental async features, SvelteKit remote functions, and explicit environment variables.

```ts
sveltekit({
Expand All @@ -95,14 +95,16 @@ sveltekit({

**Experimental async** allows `await` in Svelte markup and top-level `await` in `<script>` tags, enabling components to load data inline without separate `+page.server.js` load functions.

SvelteKit 3 removes adapter-node's runtime `ORIGIN` handling. Editable continues to use its declared `ORIGIN` variable for canonical metadata, while deployed Node servers derive request origins from trusted reverse-proxy headers by setting `PROTOCOL_HEADER=x-forwarded-proto` and `HOST_HEADER=x-forwarded-host`. Production Node deployments assume HTTPS. A direct plain-HTTP adapter-node smoke test must provide an `x-forwarded-proto: http` header when that protocol header is configured.

**Remote functions** (`$app/server`) allow server-side functions to be called directly from components via `query()` and `action()`. This replaces traditional REST endpoints for document loading and saving:

- **`src/lib/api.remote.ts`** — server-side functions for document and asset operations, called directly from components. Uses `query()` for reads and `action()` for writes. Access to `locals` (e.g. for auth checks) via `getRequestEvent()`.

**Server initialization** — `src/hooks.server.ts` exports an `init()` function (SvelteKit's `ServerInit` hook) that runs once on server startup. This is where database migration runs:

```js
import migrate from '$lib/server/migrate.js';
import migrate from '#lib/server/migrate.js';

export async function init() {
migrate();
Expand Down Expand Up @@ -381,10 +383,9 @@ Behavior rules:
neither variable is needed and neither is checked

Variables consumed through SvelteKit are declared explicitly in `src/env.ts` via
`defineEnvVars`, enabled by `experimental.explicitEnvironmentVariables` in
`vite.config.ts`. This is the SvelteKit 3 model, opted into early. Server code
imports named bindings from `$app/env/private` instead of `$env/dynamic/private`,
and the latter now throws when imported.
`defineEnvVars`; SvelteKit 3 detects this declaration file automatically. Server
code imports named bindings from `$app/env/private` instead of
`$env/dynamic/private`, and the latter throws when imported.

A declared variable is required and non-empty unless its `schema` says otherwise.
`VERCEL` and `NODE_ENV` are declared optional because each may legitimately be
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- Do NOT think 4 steps ahead or add extra features/improvements
- Only implement the specific change requested
- You can suggest what the next step could be, but don't implement it
- For anything that runs from the home route (`/`) in no-backend / Vercel mode, do NOT add top-level imports of backend-only modules like `$lib/api.remote.js` or anything that pulls in `$lib/server/db.js`
- For anything that runs from the home route (`/`) in no-backend / Vercel mode, do NOT add top-level imports of backend-only modules like `#lib/api.remote.js` or anything that pulls in `#lib/server/db.js`
- In home-route server files, import backend-only code lazily inside the `has_backend` guard so static/Vercel deployments do not evaluate database code at module load time

**Refactoring Guidelines:**
Expand Down
11 changes: 11 additions & 0 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Implementation plan

## SvelteKit 3 prerelease migration

- Upgrade to `@sveltejs/kit@3.0.0-next.12`, TypeScript 6, and SvelteKit-3-compatible prerelease versions of the Node and Vercel adapters while retaining compatible Svelte, Vite, and Vite plugin ranges.
- Delete `svelte.config.js`; keep all framework and compiler configuration in the `sveltekit()` Vite plugin.
- Replace generated `$lib` imports with a package `#lib` subpath alias declared in `package.json`.
- Replace deprecated `invalidateAll()` calls with `refreshAll()`.
- Pass the current pathname explicitly to the page-browser remote query because SvelteKit 3 forbids reading `event.url` inside queries.
- Update dynamic `$app/paths.resolve` calls to use SvelteKit 3 pathname inputs without a leading slash, while retaining route ids for known routes.
- Preserve canonical metadata through Editable's explicit `ORIGIN` variable, and configure Node deployments to trust `x-forwarded-proto` and `x-forwarded-host` now that adapter-node no longer reads `ORIGIN`.
- Validate with `pnpm check`, `pnpm build`, and a Vercel-mode build; do not run the test suite automatically.

## TypeScript conversion

Convert the whole codebase from JS+JSDoc to TypeScript per the "Language: TypeScript" section in [ARCHITECTURE.md](ARCHITECTURE.md). Non-strict; svelte-check must stay at 0 errors.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ Pass those paths to primitives and use the same paths to read values when layout
<script lang="ts">
import { get_svedit_context } from '../svedit_context.js';
import type { DocumentPath } from 'svedit';
import type { Nodes } from '$lib/document_schema.js';
import type { Nodes } from '#lib/document_schema.js';

const svedit = get_svedit_context();
let { path }: { path: DocumentPath } = $props();
Expand Down Expand Up @@ -589,7 +589,7 @@ Create `src/routes/components/Hero.svelte`. It reads the node at `path`, renders
<script lang="ts">
import { Node, TextProperty } from 'svedit';
import type { DocumentPath } from 'svedit';
import type { Nodes } from '$lib/document_schema.js';
import type { Nodes } from '#lib/document_schema.js';
import { get_svedit_context } from '../svedit_context.js';
import MediaProperty from './MediaProperty.svelte';
import { TW_LIMITER, TW_PAGE_PADDING_X } from '../tailwind_theme.js';
Expand Down Expand Up @@ -708,11 +708,11 @@ Set the secrets. `ORIGIN` must be your app's public URL, so canonical links and
```sh
fly secrets set \
ORIGIN="https://my-site.fly.dev" \
BODY_SIZE_LIMIT='30000000' \
BODY_SIZE_LIMIT='100M' \
ADMIN_PASSWORD='pick-a-strong-password'
```

`ORIGIN` must exactly match the URL you use in the browser, including the scheme and subdomain (for example, `https://example.com` and `https://www.example.com` are different origins). An incorrect value causes login and other write requests to fail with `403 Forbidden` before the password is checked. Update this secret whenever you switch to a custom domain, and access the site through that canonical URL.
`ORIGIN` should exactly match the canonical URL you use in the browser, including the scheme and subdomain (for example, `https://example.com` and `https://www.example.com` are different origins), so generated canonical and social metadata stays correct. Update this secret whenever you switch to a custom domain.

Optionally set `ASSET_GRACE_PERIOD_DAYS` (default 7): unreferenced asset files are kept on disk this many days after losing their last reference. This is also the safe window for rolling back a database backup against the live assets folder without ending up with dead image references.

Expand Down
7 changes: 6 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ services:
# inside the container and content silently dies with each deploy
# (fly.toml sets the same for Fly deployments).
DATA_DIR: /data
BODY_SIZE_LIMIT: '${BODY_SIZE_LIMIT:-30000000}'
# Must stay above MAX_VIDEO_FILESIZE (50 MiB) with room to spare: the
# transcoder treats that as a target it may overshoot, and pre-optimized
# videos skip the transcode entirely and upload at their original size.
BODY_SIZE_LIMIT: '${BODY_SIZE_LIMIT:-100M}'
PROTOCOL_HEADER: x-forwarded-proto
HOST_HEADER: x-forwarded-host
volumes:
# Host data location: ./data next to this file by default (local and
# hand-managed runs); vps-deploy.sh pins it to /data via .deploy_env so
Expand Down
5 changes: 3 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import svelte from 'eslint-plugin-svelte';
import typescript from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import globals from 'globals';
import svelteConfig from './svelte.config.js';

const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));

Expand All @@ -27,7 +26,9 @@ export default [
},
{
files: ['**/*.svelte', '**/*.svelte.js', '**/*.svelte.ts'],
languageOptions: { parserOptions: { svelteConfig, parser: typescriptParser } },
// No svelteConfig: this project has no svelte.config.js — the Svelte/Kit
// options live in vite.config.ts instead.
languageOptions: { parserOptions: { parser: typescriptParser } },
plugins: {
'@typescript-eslint': typescript
},
Expand Down
1 change: 1 addition & 0 deletions fly.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ primary_region = "fra"

[env]
DATA_DIR = "/data"
PROTOCOL_HEADER = "fly-forwarded-proto"

[deploy]
strategy = "immediate"
Expand Down
20 changes: 12 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
"name": "editable-website",
"private": true,
"version": "2.0.0",
"packageManager": "pnpm@11.15.1",
"packageManager": "pnpm@11.17.0",
"type": "module",
"imports": {
"#lib": "./src/lib/index.js",
"#lib/*": "./src/lib/*"
},
"engines": {
"node": ">=24"
},
Expand Down Expand Up @@ -38,9 +42,9 @@
"devDependencies": {
"@eslint/compat": "^2.0.3",
"@eslint/js": "^10.0.1",
"@sveltejs/adapter-vercel": "^6.3.4",
"@sveltejs/kit": "^2.63.0",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@sveltejs/adapter-vercel": "7.0.0-next.3",
"@sveltejs/kit": "3.0.0-next.12",
"@sveltejs/vite-plugin-svelte": "^7.2.0",
"@tailwindcss/forms": "^0.5.11",
"@tailwindcss/vite": "^4.2.2",
"@types/node": "^25",
Expand All @@ -53,17 +57,17 @@
"prettier": "^3.8.1",
"prettier-plugin-svelte": "^3.5.1",
"prettier-plugin-tailwindcss": "^0.7.2",
"svelte": "^5.56.2",
"svelte": "^5.56.7",
"svelte-check": "^4.6.0",
"tailwindcss": "^4.2.2",
"typescript": "^5.9.3",
"vite": "^8.0.1",
"typescript": "^6.0.3",
"vite": "^8.1.5",
"vitest": "^4.1.10"
},
"dependencies": {
"@jsquash/webp": "^1.5.0",
"@mediabunny/aac-encoder": "^1.50.9",
"@sveltejs/adapter-node": "^5.5.7",
"@sveltejs/adapter-node": "6.0.0-next.6",
"aws4fetch": "^1.0.20",
"mdast-util-from-markdown": "^2.0.3",
"mediabunny": "^1.50.9",
Expand Down
Loading