dev.md
Rama main · réplica de lectura del repositorio original.
# Jasper — Development & Build Guide
**English** · [中文](#中文)
For everyday use, see the [Docker quick start](README.md#quick-start). This guide
covers running from source, the single-binary build, the demo library, the
browser-only WASM preview, and the environment variables.
## Running from source
Requirements: a Rust toolchain and [pnpm](https://pnpm.io/).
```bash
# Backend — serves the API and the built UI on http://127.0.0.1:27583
cd server && cargo run # uses saved config, or the setup wizard
cd server && cargo run -- /path/to/JoplinDir # bootstrap with a local folder
cd server && cargo run -- https://host/dav/ # or a WebDAV URL
# Frontend
cd web && pnpm install
cd web && pnpm build # produces web/dist, served by the backend at :27583
cd web && pnpm dev # or hot-reload dev server on :5173 (proxies /api)
# Tests (see the Tests section below for the full suite)
cd server && cargo test # Rust unit tests
cd web && pnpm test # frontend unit tests (Vitest)
```
Then open **http://127.0.0.1:27583/**. On first run the wizard lets you pick
*existing library* / *new library* × *local* / *WebDAV*.
## Tests
Three layers, all run in CI ([`.github/workflows/ci.yml`](.github/workflows/ci.yml)):
```bash
# 1) Rust unit tests — core (parser / serialize / library) + server (config / storage / cache / webdav)
cd core && cargo test
cd server && cargo test
# 2) Frontend unit tests (Vitest + jsdom): render, i18n, api helpers,
# and the editor's markdown helpers (web/src/lib/editor/markdown.test.ts)
cd web && pnpm test
cd web && pnpm check # type-check (also covers *.test.ts)
# 3) Full-stack e2e (Playwright): builds the UI + a real Rust backend against a
# generated Joplin fixture, then drives a browser end-to-end.
cd web && pnpm build # UI must be built (served via JASPER_WEB_DIR)
cd server && cargo build # backend binary the e2e launcher runs
cd web && pnpm e2e:install # one-time: download the Chromium browser
cd web && pnpm e2e
```
The e2e harness lives in `web/e2e/`: `make-fixture.mjs` writes a tiny Joplin
library (a notebook, a note with an image, a to-do, a resource), `server.mjs`
is the Playwright `webServer` launcher (fresh temp data + isolated
`JASPER_CONFIG_DIR` each run), and the `*.spec.ts` files cover load/search,
read-view rendering (`smoke.spec.ts`), and the editor. Editing goes through a
single CodeMirror 6 instance with two modes — `source` and `live` preview
(default) — over one buffer that is saved byte-exact (opening or saving never
reformats the markdown): `edit.spec.ts` (a source-mode edit persists),
`editor-hook.spec.ts` (an `input`-phase plugin rewrites the buffer),
`autosave-race.spec.ts`, and `save-shortcut.spec.ts`.
## Single binary
The frontend can be **embedded into the executable** (via
[rust-embed](https://crates.io/crates/rust-embed)) so the whole app ships as one
self-contained file — no separate `web/dist` at runtime:
```bash
cd web && pnpm build # build web/dist first
cd server && cargo build --release --features embed # → server/target/release/jasper (~5 MB)
```
Copy that single binary anywhere and run it. The `embed` feature is opt-in;
without it the backend serves `web/dist` from disk as before (so plain
`cargo run` still works without building the frontend).
## Building the Docker image
```bash
docker compose up --build # then open http://localhost:27583/
```
Multi-stage build: Node builds the frontend → Rust embeds it with
`--features embed` → the runtime image is a single self-contained binary on
`debian-slim`. Published images (via
[`.github/workflows/docker.yml`](.github/workflows/docker.yml)) go to
`ghcr.io/jasper-note/jasper`: `main` builds `latest` (+ `sha-…`), and version
tags (`v1.2.3`) get semver tags.
## Try it with demo content
`docs/gen-demo-library.py` generates a small sample library (the one in the
screenshots):
```bash
python3 docs/gen-demo-library.py /tmp/jasper-demo
cd server && cargo run -- /tmp/jasper-demo
```
## Browser-only demo (WASM, no server)
The core (model / parser / serializer / index) is a dependency-light
`jasper-core` crate that also compiles to WebAssembly. With a bundled sample
library it powers a **read-only, server-less preview** that runs entirely in the
browser tab — handy for a static demo (e.g. GitHub Pages).
```bash
# needs: rustup target add wasm32-unknown-unknown + wasm-pack
cd web && pnpm build:demo # builds the wasm, then a static demo bundle into web/dist
```
It does not affect the native build: when not in demo mode the wasm import is
tree-shaken out entirely.

## Environment variables
| Variable | Default | Purpose |
| --- | --- | --- |
| `JASPER_HOST` | `127.0.0.1` | Bind address; set `0.0.0.0` for LAN / containers |
| `JASPER_PORT` | `27583` | Listen port |
| `JASPER_CONFIG_DIR` | platform config dir | Where `config.db` / `cache.db` live (`/config` in Docker) |
| `JASPER_WEB_DIR` | next to source | Static frontend dir (overrides embedded/disk defaults) |
| `JASPER_SOURCE` | — | First-run bootstrap data source (local path or WebDAV URL) |
| `JASPER_WEBDAV_USER` / `JASPER_WEBDAV_PASS` | — | WebDAV credentials for the bootstrap source |
## Architecture
```
Browser SPA (Svelte 5 + CodeMirror) ──HTTP──▶ Rust server (axum) ──read/write──▶ Local folder / WebDAV
```
The server scans the sync directory, parses every `<id>.md` item into an
in-memory index, and exposes a small JSON API (`/api/folders`, `/api/notes`,
`/api/resources`, `/api/search`, …). The SPA renders Markdown client-side and
rewrites `:/id` resource links to `/api/resources/id`. See
[`CLAUDE.md`](CLAUDE.md) for the full module layout and
[`docs/joplin-data-format.md`](docs/joplin-data-format.md) for the data format.
---
<a id="中文"></a>
# 开发与构建指南(中文)
[English](#jasper--development--build-guide) · **中文**
日常使用请看 [Docker 快速开始](README.zh-CN.md#快速开始)。本文覆盖:源码运行、
单文件打包、演示库、浏览器 WASM 预览,以及环境变量。
## 源码运行
需要:Rust 工具链与 [pnpm](https://pnpm.io/)。
```bash
# 后端 —— 在 http://127.0.0.1:27583 提供 API 并托管前端
cd server && cargo run # 读已保存配置,或进入向导
cd server && cargo run -- /路径/JoplinDir # 用本地文件夹引导
cd server && cargo run -- https://host/dav/ # 或一个 WebDAV 地址
# 前端
cd web && pnpm install
cd web && pnpm build # 产出 web/dist,由后端在 :27583 托管
cd web && pnpm dev # 或开发热更新(:5173,/api 代理到后端)
# 测试(完整套件见下方「测试」一节)
cd server && cargo test # Rust 单元测试
cd web && pnpm test # 前端单元测试(Vitest)
```
然后打开 **http://127.0.0.1:27583/**。首次启动可在向导里选择 *现有库* / *新建库*
× *本地* / *WebDAV*。
## 测试
三层,全部在 CI([`.github/workflows/ci.yml`](.github/workflows/ci.yml))里跑:
```bash
# 1) Rust 单元测试 —— core(parser / serialize / library)+ server(config / storage / cache / webdav)
cd core && cargo test
cd server && cargo test
# 2) 前端单元测试(Vitest + jsdom):渲染、i18n、api 助手,
# 以及编辑器的 markdown 助手(web/src/lib/editor/markdown.test.ts)
cd web && pnpm test
cd web && pnpm check # 类型检查(也覆盖 *.test.ts)
# 3) 全栈 e2e(Playwright):构建前端 + 真 Rust 后端(指向生成的 Joplin 测试库),
# 再用浏览器端到端驱动。
cd web && pnpm build # 需先构建前端(经 JASPER_WEB_DIR 托管)
cd server && cargo build # e2e 启动器要运行的后端二进制
cd web && pnpm e2e:install # 一次性:下载 Chromium 浏览器
cd web && pnpm e2e
```
e2e 相关代码在 `web/e2e/`:`make-fixture.mjs` 写出一个最小 Joplin 库(一个笔记本、
一篇带图笔记、一条待办、一个资源);`server.mjs` 是 Playwright 的 `webServer`
启动器(每次重建临时数据 + 隔离的 `JASPER_CONFIG_DIR`);各 `*.spec.ts` 覆盖
加载/搜索、阅读视图渲染(`smoke.spec.ts`)与编辑器。编辑器是单个 CodeMirror 6
实例、两种模式(`source` 源码 / `live` 实时预览,默认后者),共用一份缓冲、保存
逐字节一致(打开与保存都不重排 markdown):`edit.spec.ts`(源码模式编辑落盘)、
`editor-hook.spec.ts`(`input` 相位插件改写缓冲)、`autosave-race.spec.ts`、
`save-shortcut.spec.ts`。
## 单文件打包
前端可经 [rust-embed](https://crates.io/crates/rust-embed) **编译进二进制**,整个
应用就是一个自带前端的可执行文件 —— 运行时不再需要 `web/dist`:
```bash
cd web && pnpm build # 先构建出 web/dist
cd server && cargo build --release --features embed # → server/target/release/jasper(约 5 MB)
```
把这个文件拷到任意位置直接运行即可。`embed` 是可选 feature;不带它时后端照旧从磁盘
`web/dist` 托管(所以不构建前端也能直接 `cargo run`)。
## 自行构建 Docker 镜像
```bash
docker compose up --build # 然后访问 http://localhost:27583/
```
多阶段构建:Node 构建前端 → Rust 用 `--features embed` 把前端内嵌进二进制 →
运行镜像是 `debian-slim` 上的单个自带前端二进制。发布镜像(由
[`.github/workflows/docker.yml`](.github/workflows/docker.yml))推到
`ghcr.io/jasper-note/jasper`:`main` 构建 `latest`(+ `sha-…`),版本 tag
(`v1.2.3`)打语义化版本标签。
## 用演示内容试一试
`docs/gen-demo-library.py` 会生成一个小型示例库(即这些截图所用):
```bash
python3 docs/gen-demo-library.py /tmp/jasper-demo
cd server && cargo run -- /tmp/jasper-demo
```
## 浏览器 demo(WASM,无 server)
核心逻辑(数据模型 / 解析 / 序列化 / 索引)抽成了依赖很轻的 `jasper-core` crate,
它也能编译到 WebAssembly。配上内置的演示库,就能做一个**只读、零服务器**的预览——
整页跑在浏览器标签里,适合做静态演示站(如 GitHub Pages)。
```bash
# 需先装:rustup target add wasm32-unknown-unknown + wasm-pack
cd web && pnpm build:demo # 先编 wasm,再产出静态 demo 到 web/dist
```
不影响原生构建:非 demo 模式下 wasm import 会被整体 tree-shake 掉。

## 环境变量
| 变量 | 默认值 | 用途 |
| --- | --- | --- |
| `JASPER_HOST` | `127.0.0.1` | 绑定地址;局域网 / 容器设 `0.0.0.0` |
| `JASPER_PORT` | `27583` | 监听端口 |
| `JASPER_CONFIG_DIR` | 平台配置目录 | `config.db` / `cache.db` 位置(Docker 里为 `/config`) |
| `JASPER_WEB_DIR` | 源码旁 | 前端静态目录(覆盖内嵌/磁盘默认) |
| `JASPER_SOURCE` | —— | 首次引导用的数据源(本地路径或 WebDAV 地址) |
| `JASPER_WEBDAV_USER` / `JASPER_WEBDAV_PASS` | —— | 引导数据源的 WebDAV 账号密码 |
## 工作原理
```
浏览器 SPA (Svelte 5 + CodeMirror) ──HTTP──▶ Rust 服务 (axum) ──读写──▶ 本地文件夹 / WebDAV
```
服务扫描同步目录,把每个 `<id>.md` 条目解析进内存索引,对外暴露一组精简 JSON API
(`/api/folders`、`/api/notes`、`/api/resources`、`/api/search` 等)。前端在浏览器侧
渲染 Markdown,并把 `:/id` 资源链接改写为 `/api/resources/id`。完整模块划分见
[`CLAUDE.md`](CLAUDE.md),数据格式见
[`docs/joplin-data-format.md`](docs/joplin-data-format.md)。