chore: scaffold Tauri 2 + Svelte 5 workspace

Standard Tauri layout with a Svelte 5 + TypeScript + Vite frontend and a
Rust backend in src-tauri. Adds build tooling, app/window config,
capabilities, generated icons and the project README.
This commit is contained in:
2026-05-22 16:27:11 +03:00
commit dfafea41f4
66 changed files with 3326 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Rust / Tauri
/src-tauri/target
/src-tauri/gen/schemas
/target
# Node
node_modules
dist
dist-ssr
.DS_Store
# Vite
*.local
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor
.vscode/*
!.vscode/extensions.json
.idea

127
README.md Normal file
View File

@@ -0,0 +1,127 @@
# Mermix
A cross-platform **Mermaid diagram editor & viewer** with **Git-backed projects**.
Organize many diagrams into projects, edit them with a live preview, export to
SVG/PNG, and version everything with real Git branches and commits — all in a
native desktop app.
Built with **Rust + Tauri 2**, **SQLite (sqlx)**, **git2**, and a **Svelte 5 +
TypeScript** frontend using **CodeMirror 6** and **Mermaid 11**.
---
## Features
- **Projects** — each project is a folder on disk that is a real Git repository
with a `mermix.toml` config and a `diagrams/` directory of `.mmd` files.
- **Many diagrams per project** with a sidebar explorer; create, rename, delete.
- **Live editor + preview** — CodeMirror source on the left, debounced Mermaid
render on the right, with zoom/pan and inline syntax-error reporting.
- **Git version control built in** — view the working-tree status, write commit
messages, browse history, create branches and switch between them. Each branch
keeps its own set of diagrams, just like normal Git.
- **Export** the current diagram to **SVG** or **PNG** (2× scale).
- **Project registry** — recently opened projects are remembered in a local
SQLite database so you can jump back in from the start screen.
- **Themes** — switch the Mermaid theme (default / neutral / dark / forest /
base) per project.
## Architecture
```
Mermix/
├─ src/ # Svelte 5 + TS frontend
│ ├─ App.svelte # layout, resizable editor/preview split
│ ├─ lib/
│ │ ├─ api.ts # typed wrappers over Tauri commands
│ │ ├─ store.svelte.ts # central rune-based app state
│ │ ├─ mermaid.ts # render + error capture
│ │ ├─ export.ts # SVG / PNG export via save dialog
│ │ └─ components/ # Sidebar, Editor, Preview, GitPanel, Toolbar, …
│ └─ main.ts
└─ src-tauri/ # Rust backend
├─ src/
│ ├─ lib.rs # Tauri builder, state, command registry
│ ├─ commands.rs # the IPC surface
│ ├─ db.rs # sqlx/SQLite project registry + settings
│ ├─ project.rs # mermix.toml, create/open, slugify
│ ├─ diagram.rs # .mmd CRUD, frontmatter titles
│ ├─ git_ops.rs # git2: commit, history, branches, status
│ ├─ error.rs / state.rs
│ └─ tests.rs # headless core-logic tests
└─ tauri.conf.json
```
**Data model.** Mermix keeps one small app-level SQLite database (in the OS app
data directory) that only tracks the *registry* of known projects and user
settings. All real content lives on disk inside each project's Git repository —
so projects are portable, inspectable, and work with any other Git tooling.
A project on disk looks like:
```
my-diagrams/
├─ mermix.toml # project id, name, description, default theme
├─ README.md
├─ .gitignore
├─ .git/ # full Git history
└─ diagrams/
├─ welcome.mmd
└─ login-flow.mmd # Mermaid source, optional YAML frontmatter title
```
## Prerequisites
- **Rust** (stable) and **Cargo**
- **Node.js** 18+ and npm
- Platform webview deps:
- **macOS** — nothing extra (system WebKit)
- **Windows** — WebView2 (preinstalled on Windows 11)
- **Linux** — `webkit2gtk` and related packages (see Tauri docs)
## Getting started
```bash
npm install # install frontend deps + Tauri CLI
npm run app:dev # run the desktop app in dev mode (hot reload)
npm run app:build # build a production bundle for your platform
```
Other useful scripts:
```bash
npm run dev # frontend only (Vite) on http://localhost:1420
npm run build # type-check + build the frontend bundle
npm run check # svelte-check type checking
```
Backend logic is covered by headless tests:
```bash
cd src-tauri && cargo test
```
## Keyboard shortcuts
| Shortcut | Action |
| ------------------- | --------------------- |
| `⌘/Ctrl + S` | Save current diagram |
| `⌘/Ctrl + Enter` | Commit (in commit box)|
| `⌘/Ctrl + scroll` | Zoom the preview |
## How Git is used
- Creating a project runs `git init` (default branch `main`) and makes an
initial commit.
- **Commit** stages every change (adds, edits, deletes) and records it with the
message you type; the author defaults to your global Git identity, falling back
to `Mermix <mermix@localhost>` if none is configured.
- **Branches** are listed in the Git panel; create one and Mermix checks it out
for you. Switching branches reloads the diagram list from that branch.
- **History** shows the most recent commits with short SHA, message, author and
relative time.
## License
MIT

13
index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/mermix.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mermix</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

2909
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

35
package.json Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "mermix",
"version": "0.1.0",
"description": "Cross-platform Mermaid diagram editor & viewer with Git-backed projects",
"type": "module",
"scripts": {
"dev": "vite",
"build": "svelte-check --tsconfig ./tsconfig.json && vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json",
"tauri": "tauri",
"app:dev": "tauri dev",
"app:build": "tauri build"
},
"dependencies": {
"@codemirror/commands": "^6.6.0",
"@codemirror/language": "^6.10.2",
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.28.0",
"@tauri-apps/api": "^2.0.0",
"@tauri-apps/plugin-dialog": "^2.0.0",
"codemirror": "^6.0.1",
"mermaid": "^11.2.0"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@tauri-apps/cli": "^2.0.0",
"@tsconfig/svelte": "^5.0.4",
"svelte": "^5.1.0",
"svelte-check": "^4.0.0",
"tslib": "^2.7.0",
"typescript": "^5.6.0",
"vite": "^5.4.0"
}
}

21
public/mermix.svg Normal file
View File

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#a78bfa"/>
<stop offset="1" stop-color="#7c5cff"/>
</linearGradient>
</defs>
<rect width="256" height="256" rx="56" fill="#171a21"/>
<g fill="none" stroke="url(#g)" stroke-width="14" stroke-linecap="round" stroke-linejoin="round">
<rect x="48" y="40" width="72" height="44" rx="10"/>
<rect x="136" y="40" width="72" height="44" rx="10"/>
<rect x="92" y="160" width="72" height="44" rx="10"/>
<path d="M84 84 L128 160"/>
<path d="M172 84 L128 160"/>
</g>
<g fill="url(#g)">
<circle cx="84" cy="84" r="9"/>
<circle cx="172" cy="84" r="9"/>
<circle cx="128" cy="160" r="9"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 826 B

53
src-tauri/Cargo.toml Normal file
View File

@@ -0,0 +1,53 @@
[package]
name = "mermix"
version = "0.1.0"
description = "Cross-platform Mermaid editor & viewer with Git-backed projects"
authors = ["Mermix"]
edition = "2021"
rust-version = "1.77"
[lib]
# The `_lib` suffix keeps the library name distinct from the binary so the
# Tauri mobile bindings can link against it.
name = "mermix_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-dialog = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# App-level metadata store (project registry + settings).
sqlx = { version = "0.8", default-features = false, features = [
"runtime-tokio",
"sqlite",
"chrono",
"macros",
] }
# Per-project version control.
git2 = "0.19"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs", "sync"] }
thiserror = "1"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["v4", "serde"] }
directories = "5"
toml = "0.8"
[features]
# This feature is used for production builds or when a dev server is not
# specified, DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]
[profile.release]
panic = "abort"
codegen-units = 1
lto = true
opt-level = "s"
strip = true

3
src-tauri/build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@@ -0,0 +1,16 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Default permissions for the Mermix main window",
"windows": ["main"],
"permissions": [
"core:default",
"core:window:allow-start-dragging",
"core:window:allow-set-title",
"dialog:default",
"dialog:allow-open",
"dialog:allow-save",
"dialog:allow-message",
"dialog:allow-ask"
]
}

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src-tauri/icons/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#fff</color>
</resources>

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

44
src-tauri/tauri.conf.json Normal file
View File

@@ -0,0 +1,44 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Mermix",
"version": "0.1.0",
"identifier": "dev.mermix.app",
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "npm run build",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "Mermix",
"width": 1280,
"height": 820,
"minWidth": 880,
"minHeight": 560,
"resizable": true,
"fullscreen": false,
"center": true
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"category": "DeveloperTool",
"shortDescription": "Mermaid editor & viewer with Git-backed projects",
"longDescription": "Mermix is a cross-platform Mermaid diagram editor and viewer. Organize many diagrams into projects backed by Git for branches, versions and commit history."
},
"plugins": {}
}

5
svelte.config.js Normal file
View File

@@ -0,0 +1,5 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
export default {
preprocess: vitePreprocess(),
};

20
tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
"allowJs": true,
"checkJs": true,
"isolatedModules": true,
"moduleDetection": "force",
"moduleResolution": "bundler",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.svelte", "vite.config.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}

11
tsconfig.node.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}

36
vite.config.ts Normal file
View File

@@ -0,0 +1,36 @@
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
const host = process.env.TAURI_DEV_HOST;
// https://vitejs.dev/config/
export default defineConfig(async () => ({
plugins: [svelte()],
// Tauri expects a fixed port and fails if it is not available.
clearScreen: false,
server: {
port: 1420,
strictPort: true,
host: host || false,
hmr: host
? {
protocol: 'ws',
host,
port: 1421,
}
: undefined,
watch: {
// Don't watch the Rust backend; tauri handles that.
ignored: ['**/src-tauri/**'],
},
},
// Produce smaller, debuggable bundles depending on the Tauri build profile.
envPrefix: ['VITE_', 'TAURI_'],
build: {
target: process.env.TAURI_ENV_PLATFORM === 'windows' ? 'chrome105' : 'safari13',
minify: !process.env.TAURI_ENV_DEBUG ? 'esbuild' : false,
sourcemap: !!process.env.TAURI_ENV_DEBUG,
},
}));