Repository-specific guidance for coding agents working on Svedit.
README.md is the user-facing manual and source of truth for setup, customization, and usage patterns. INTEGRATION.md covers running Editable inside larger systems: backends, hosting several sites, and custom sign-in. Consult only the relevant sections when working on those areas; keep agent-specific guidance here.
README.md is also served as the manual at /manual, for people building sites. Never link to .md files from it, and leave out what INTEGRATION.md covers, such as backends, custom sign-in, and hosting several sites; a test converts every configured Markdown page and fails on links it cannot render.
Editable is minimalistic by design: less, but better.
- Correctness first, optimization later. Build on solid foundations: deterministic behavior, explicit data, and no guessing. Add optimizations as a separate, optional layer, and only once the foundation is right.
- Keep the code small. Every line is maintained forever. Client-side code matters most, because it is loaded for every page. A performance optimization that adds noticeable code for a small gain is not justified.
- Remove code where it is justified. Eliminate redundancy, dead code, and needless abstraction when the result is clearly simpler and still correct. Do not shrink code at the expense of clarity or correctness.
- Avoid growth at all costs. Prefer extending an existing mechanism over adding a new one. Before adding code, check whether the problem can be solved with less, and say so when a change grows the codebase noticeably.
- Write idiomatic code. Sites are built by adapting Editable, and its patterns get copied and repeated. Use the most idiomatic Svelte, SvelteKit, and Svedit patterns, so that what gets copied is worth copying.
- For code running from
/in no-backend/Vercel mode, do not add top-level imports of backend-only modules such as#app/api.remote.jsor anything that imports a backend from#lib/server/backends/. - In home-route server files, import backend-only code lazily inside the
has_backendguard so static deployments do not evaluate database code at module load time. - Files using Svelte runes (
$state,$derived,$effect, etc.) must use the.svelte.jsor.svelte.tsextension. - Use sentence case for documentation headings and comments; capitalize
Sveditas a proper noun. - Read the site's origin, base path, and languages through
sitefrom#app/server_site.json the server and#app/client_site.jsin the browser, never fromORIGINorLANGUAGESdirectly: a host serving several sites sets them per request. Documents reach the browser with internal links already under the base, so components rendernode.hrefas is. Links the browser makes up itself, such as asset URLs and navigation targets, go throughresolve_href(href, site)from#app/document_links.js; absolute URLs useabsolute_page_url(path, site). Stored links never include the base. - Use
backendand the asset helpers from#app/services.jsonly inside functions: they go to the current request's site backend. Every backend implements the asynchronousBackendinterface insrc/lib/server/backend.ts; the built-in SQLite backend lives insrc/lib/server/backends/. Each backend function is atomic, so writes that belong together go into one call, such assave_documents; there are no transactions. Await every backend call. SQL exists only in the SQLite backend (src/lib/server/backends/sqlite_backend.ts) and in migrations. Migrations live in a folder per backend, such assrc/app/migrations/sqlite, and each backend runs its folder as one chain, in id order and as one transaction. A migration changes documents with the content helpers and everything else with the backend's storage, spread next to them, such asdb; create one withpnpm migration:create <name>. Editable computes what it stores: the asset references a document uses are rebuilt into theasset_refscollection on every write, and link references intodocument_refs.
-
Use tabs for indentation throughout the project, including code snippets in documentation and design system examples. Preserve spaces only where the file format requires them.
-
Use
snake_casefor project-defined JavaScript and TypeScript identifiers. Keep web platform and Svelte APIs in their nativecamelCaseform. -
Prefer Tailwind classes and minimize custom CSS. For CSS custom properties, use Tailwind's arbitrary-value utilities, such as
text-(--editing)andborder-(--editing), where applicable. -
Prefix all project-defined CSS classes with
ew-, including component, state, and custom utility classes. Standard Tailwind classes and classes required by third-party libraries retain their original names.
- The person who makes a commit is its author. Do not sign commits or pull requests with an agent name: no
Co-Authored-Bytrailers, "Generated with" lines, or other agent attribution in commit messages or pull request descriptions.
- The user manually verifies UI changes. Do not use a browser, screenshots, or computer-use tools to verify the UI unless the user explicitly asks.
- Use appropriate code checks, such as formatting, linting, and Svelte diagnostics, without starting a browser-based verification workflow.
-
Inline formatting styles (emphasis, strong, link, code, and highlight) are mutually exclusive. Never nest them or apply multiple styles to the same text.
-
Consult the design system source before styling UI. With the development server running, the user can open the design system in a browser to inspect typography, spacing, buttons, and editor pills, and inspect the source for their recipes. Shared tokens and typography utilities live in
src/app.css. Follow the UI verification rules above for agent browser use. -
Keep the design system focused on reusable primitives, such as typography, spacing, colors, controls, and cards. Higher-level compositions and behavior belong in application code and components: for example, document the reusable Card primitive in the design system, while galleries and carousels compose it in application components.
-
Keep reference examples as explicit HTML, SVG, and Tailwind classes. Repetition is intentional; application components own behavior and may adapt the recipes when their interaction requires it.
-
Update existing primitive examples when component changes affect those primitives. Do not automatically add examples for every new component or layout. If unsure whether a new example belongs in the design system, ask the user before adding it.
Svedit is a Svelte 5 rich content editor built around a graph-based document model.
Sessionowns document state, transactions, and history;Transactionperforms atomic changes.- Documents are graphs of nodes with properties and references.
- Within a document, nodes (including image, video, mark, and annotation nodes) have exactly one owning
nodefield,node_arrayentry, mark range, or annotation range. Do not reference the same node from multiple fields, array entries, or ranges; copy it with a fresh id instead. Image/video edits and same-type file replacement preserve the owned node's id; changing type or pasting a copied node creates a new id. - Navigation, footer, and the site banner are shared as separate documents referenced by pages; their child nodes still follow the single-owner rule. Multiple image/video nodes may use the same asset
src. - Each text mark or annotation range owns its attachment node. Splitting or duplicating a range must create an independent attachment node for the additional occurrence, so editing a link in one place cannot change another.
- Selection supports text, node, and property selections and maps between the model and the DOM.
Svedit.sveltemanages the editor and selection;NodeArrayProperty.svelterenders node sequences;TextProperty.svelterenders editable text with marks and annotations.
- Give wrappers that scroll editable node arrays
relativepositioning, as inNav.svelte. Svedit's absolutely positioned node gaps and markers need a containing block that moves with the scrolling content. Keep theNodeelements themselves static. - Put horizontal gutters inside the scroll container so the first and last node gaps have space outside the content. Match
scroll-paddingto those gutters so snapped cards retain their alignment. - Put carousel gutters on a track wrapper around
NodeArrayProperty, not on the node array itself. Padding after the final node can trigger Svedit's trailing-gap fill/clamp calculations and place the marker over earlier cards. - Do not add
tabindexto scroll wrappers in editing mode: taking focus from Svedit's canvas prevents it from processing text selections. A focusable scroll region is appropriate in viewing mode.
When adding a property to a node type, update both:
document_schemainsrc/app/document_schema.tsinsertersinsrc/app/document_config.ts
For Svelte or SvelteKit questions, use the Svelte MCP documentation tools:
- Call
list-sectionsfirst. - Use its
use_casesinformation to identify all relevant sections. - Retrieve those sections with
get-documentation. - When writing Svelte code, run
svelte-autofixerand address all reported issues or suggestions before delivery.