Skip to content
Closed
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
8 changes: 8 additions & 0 deletions .gemini/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"svelte": {
"command": "npx",
"args": ["-y", "@sveltejs/mcp"]
}
}
}
13 changes: 13 additions & 0 deletions CLAUDE.md → AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Commands

**Development:**

- `npm run dev` - Start development server
- `npm run build` - Build for production
- `npm run preview` - Preview production build

**Implementation Guidelines:**

- Before implementing any feature, read `ARCHITECTURE.md` for design decisions and `IMPLEMENTATION_PLAN.md` for the step-by-step implementation spec
- Design decisions go in `ARCHITECTURE.md`, implementation steps go in `IMPLEMENTATION_PLAN.md`
- New features must be specified before implementation begins — the spec should be concise but sufficient to derive the implementation from
Expand All @@ -20,22 +22,26 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- You can suggest what the next step could be, but don't implement it

**Refactoring Guidelines:**

- During refactors, make ONLY the minimal changes needed (e.g., renaming APIs)
- Do NOT "improve" or restructure logic while refactoring
- If you see something that could be improved, note it separately for a future task
- Refactoring and improving are two separate activities - never combine them

**Code Style:**

- Use snake_case for all variable names, function names, and identifiers
- This applies to JavaScript/TypeScript code, test files, and any new code written

**Styling:**

- Use Tailwind CSS classes whenever possible
- Minimize custom CSS - only use it for things Tailwind can't handle (e.g., CSS custom properties like `var(--svedit-editing-stroke)`)
- Use Tailwind's arbitrary value syntax for custom properties: `text-(--svedit-editing-stroke)`, `border-(--svedit-editing-stroke)`
- Do not use rounded corners (keep elements rectangular)

**What to NOT change (keep camelCase):**

- `window.getSelection()` - native API
- `document.activeElement` - native API
- `navigator.clipboard` - native API
Expand All @@ -49,9 +55,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
**Pattern**: If it's a web platform API or Svelte API, keep camelCase. If it's our custom variable/function name, use snake_case.

**File Extensions:**

- Files using Svelte runes (`$state`, `$derived`, `$effect`, etc.) must use `.svelte.js` or `.svelte.ts` extension

**Documentation Style:**

- Use sentence case for all headings in documentation (README.md, etc.)
- Use sentence case for code comments
- Sentence case means: capitalize only the first word and proper nouns
Expand All @@ -72,16 +80,19 @@ Svedit is a rich content editor template built with Svelte 5 that uses a graph-b
### Core Components

**Document Model:**

- `Document` - Central document class with state management, transactions, and history
- `Tras` - Handles atomic operations on the document
- Documents are represented as graphs of nodes with properties and references

**Selection:**

- Supports text, node, and property selections
- Maps between internal selection model and DOM selection
- Handles complex selection scenarios like backwards selections and multi-node selections

**Key Components:**

- `Svedit.svelte` - Main editor component with event handling and selection management
- `NodeArrayProperty.svelte` - Renders containers that hold sequences of nodes
- `AnnotatedTextProperty.svelte` - Handles annotated text rendering and editing
Expand All @@ -90,6 +101,7 @@ Svedit is a rich content editor template built with Svelte 5 that uses a graph-b
### Schema

Content is defined through schemas that specify:

- Node types and their properties
- Property types: `string`, `integer`, `boolean`, `string_array`, `annotated_text`, `node`, `node_array`
- Reference relationships between nodes
Expand All @@ -105,6 +117,7 @@ Content is defined through schemas that specify:
## Schema and Inserter

When adding new properties to a node type:

1. Add to schema in `create_session.js` (`document_schema`)
2. Add to inserter in `create_session.js` (`inserters`)

Expand Down
129 changes: 82 additions & 47 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ The app uses Svelte's experimental async features and SvelteKit's remote functio

```js
const config = {
kit: {
adapter: adapter(),
experimental: {
remoteFunctions: true
}
},
compilerOptions: {
experimental: {
async: true
}
}
kit: {
adapter: adapter(),
experimental: {
remoteFunctions: true
}
},
compilerOptions: {
experimental: {
async: true
}
}
};
```

Expand All @@ -40,7 +40,7 @@ const config = {
import migrate from '$lib/server/migrate.js';

export async function init() {
migrate();
migrate();
}
```

Expand Down Expand Up @@ -205,33 +205,33 @@ There are two media node types: `image` and `video`. Each has the same visual pr

```json
{
"id": "feature_1_image",
"type": "image",
"src": "c4b519da4c0a6512b5d9519aac0d9df7fab9152a6df109515456ada4702fabdb.webp",
"width": 1600,
"height": 900,
"alt": "Feature image",
"scale": 1.0,
"focal_point_x": 0.5,
"focal_point_y": 0.5,
"object_fit": "cover"
"id": "feature_1_image",
"type": "image",
"src": "c4b519da4c0a6512b5d9519aac0d9df7fab9152a6df109515456ada4702fabdb.webp",
"width": 1600,
"height": 900,
"alt": "Feature image",
"scale": 1.0,
"focal_point_x": 0.5,
"focal_point_y": 0.5,
"object_fit": "cover"
}
```

**`video`** — MP4 (WebM may be added later):

```json
{
"id": "hero_video",
"type": "video",
"src": "e7a3f1bc...abcd.mp4",
"width": 1920,
"height": 1080,
"alt": "Product demo",
"scale": 1.0,
"focal_point_x": 0.5,
"focal_point_y": 0.5,
"object_fit": "cover"
"id": "hero_video",
"type": "video",
"src": "e7a3f1bc...abcd.mp4",
"width": 1920,
"height": 1080,
"alt": "Product demo",
"scale": 1.0,
"focal_point_x": 0.5,
"focal_point_y": 0.5,
"object_fit": "cover"
}
```

Expand Down Expand Up @@ -297,8 +297,8 @@ The paste handler uses `get_media_type(file)` to map each file's MIME type to a
```js
/** @returns {'image' | 'video'} */
function get_media_type(file) {
if (file.type.startsWith('video/')) return 'video';
return 'image';
if (file.type.startsWith('video/')) return 'video';
return 'image';
}
```

Expand Down Expand Up @@ -384,13 +384,13 @@ variant URL = /assets/{stem}/w320.webp

```html
<img
src="/assets/c4b519da...fabdb.webp"
srcset="
/assets/c4b519da...fabdb/w320.webp 320w,
/assets/c4b519da...fabdb/w640.webp 640w,
/assets/c4b519da...fabdb/w1024.webp 1024w,
/assets/c4b519da...fabdb.webp 1600w
"
src="/assets/c4b519da...fabdb.webp"
srcset="
/assets/c4b519da...fabdb/w320.webp 320w,
/assets/c4b519da...fabdb/w640.webp 640w,
/assets/c4b519da...fabdb/w1024.webp 1024w,
/assets/c4b519da...fabdb.webp 1600w
"
/>
```

Expand All @@ -402,12 +402,12 @@ Different media types are handled differently:

The asset id always includes the file extension. The stem (id without extension) is used to derive the variant directory.

| Type | Node type | Client processing | Asset id example | Variants |
|---|---|---|---|---|
| Static images (JPEG, PNG, WebP, HEIC) | `image` | Resize to `MAX_IMAGE_WIDTH`, convert to WebP via WASM | `c4b519da...fabdb.webp` | Yes (`c4b519da...fabdb/w320.webp`, etc.) |
| Animated GIFs | `image` | Passthrough | `c4b519da...fabdb.gif` | No |
| SVGs | `image` | Passthrough | `c4b519da...fabdb.svg` | No |
| Videos (MP4) | `video` | Passthrough | `c4b519da...fabdb.mp4` | No |
| Type | Node type | Client processing | Asset id example | Variants |
| ------------------------------------- | --------- | ----------------------------------------------------- | ----------------------- | ---------------------------------------- |
| Static images (JPEG, PNG, WebP, HEIC) | `image` | Resize to `MAX_IMAGE_WIDTH`, convert to WebP via WASM | `c4b519da...fabdb.webp` | Yes (`c4b519da...fabdb/w320.webp`, etc.) |
| Animated GIFs | `image` | Passthrough | `c4b519da...fabdb.gif` | No |
| SVGs | `image` | Passthrough | `c4b519da...fabdb.svg` | No |
| Videos (MP4) | `video` | Passthrough | `c4b519da...fabdb.mp4` | No |

### Image size constraints

Expand Down Expand Up @@ -618,6 +618,41 @@ In `hooks.server.js`, on every request:
- `POST /api/login` — authenticate with `{ password }`, sets session cookie
- `POST /api/logout` — clears session cookie, deletes session row

## Highlights block

The highlights block is a compact list for property-style metadata (e.g. "Units: 8"). It renders as a flex list with `justify-between`, and each row has a bottom border underline.

Schema:

- `highlights` (block)
- `items` — `node_array` of `highlight_item`
- `highlight_item` (block)
- `label` — `annotated_text`, single line
- `value` — `annotated_text`, single line

Editing:

- A visible "add row" handle appears below the list in edit mode.

## Columns block

The columns block is a parent layout container that holds multiple column node arrays. Each column can contain any standard page body block (prose, hero, gallery, etc.), enabling multi-column sections without changing the page body structure.

Schema:

- `columns` (block)
- `layout` — integer layout selector
- `columns` — `node_array` of `column`
- `column` (block)
- `content` — `node_array` of page body block types

Layouts:

- Layout 1: two equal columns (`md:grid-cols-2`)
- Layout 2: three equal columns (`md:grid-cols-3`)

Mobile behavior: columns stack into a single column (`grid-cols-1`).

## Future: optional S3 storage

Assets are stored on the local filesystem (`ASSET_PATH`) by default. This keeps the app fully self-contained — a single deployment with no external dependencies beyond the server itself. For most sites this is sufficient.
Expand Down
Loading