This file contains guidelines for AI agents working on the Markpad codebase.
Markpad is a Tauri v2 application with:
- Frontend: Svelte 5 + TypeScript + Vite
- Backend: Rust (Tauri commands)
- Purpose: Markdown viewer and text editor
# Development
npm run dev # Start Vite dev server
npm run dev:installer # Run in installer mode
# Building
npm run build # Build frontend for production
npm run preview # Preview production build
# Type Checking
npm run check # Run svelte-check (type checking)
npm run check:watch # Watch mode type checkingcd src-tauri
cargo build # Build Rust code
cargo build --release # Release build
# Testing
cargo test # Run all Rust tests
cargo test <test_name> # Run specific test
# Linting/Formatting
cargo check # Check for errors
cargo clippy # Run clippy linter
cargo fmt # Format codenpm run tauri dev # Run Tauri in dev mode
npm run tauri build # Build Tauri application- Indentation: Use tabs (not spaces)
- Quotes: Single quotes for strings
- Semicolons: Optional but be consistent
- Strict Mode: Enabled - always define types explicitly
- Group imports: external libs first, then internal modules
- Use
.jsextension for relative imports (SvelteKit convention) - Example:
import { onMount } from 'svelte';
import { tabManager } from '../stores/tabs.svelte.js';Always use Svelte 5 runes for reactivity:
$state()for reactive state$derived()for computed values$effect()for side effects$props()for component props with explicit types$bindable()for two-way binding
- Components: PascalCase (e.g.,
Editor.svelte) - Stores: camelCase with
.svelte.tsextension (e.g.,tabs.svelte.ts) - Functions/Variables: camelCase
- Types/Interfaces: PascalCase
- Event handlers: Prefix with
on(e.g.,onsave,onnew)
Always type props explicitly using $props():
let {
value = $bindable(),
onsave,
theme = 'system',
} = $props<{
value: string;
onsave?: () => void;
theme?: 'system' | 'light' | 'dark';
}>();- Indentation: 4 spaces
- Error Handling: Use
Result<T, String>for Tauri commands, propagate with? - Naming: snake_case for functions/variables, PascalCase for types
Always mark with #[tauri::command] and return Result<T, String>:
#[tauri::command]
fn read_file_content(path: String) -> Result<String, String> {
fs::read_to_string(path).map_err(|e| e.to_string())
}Use conditional compilation for platform-specific features:
#[cfg(target_os = "windows")]
// Windows-specific code
#[cfg(not(target_os = "windows"))]
// Fallback for other platformssrc/
lib/
components/ # Svelte components
stores/ # Svelte 5 stores (.svelte.ts)
routes/ # SvelteKit routes
src-tauri/
src/
main.rs # Entry point
lib.rs # Main library with Tauri commands
setup.rs # Installation/setup logic
- Use Svelte 5 runes-based stores in
src/lib/stores/ - Export singleton instances (e.g.,
export const tabManager = new TabManager())
Use invoke() from @tauri-apps/api for Rust commands:
import { invoke } from '@tauri-apps/api';
const result = await invoke<string>('command_name', { arg: value });All file operations go through Rust commands - never use Node.js fs APIs.
- Rust:
cargo testinsrc-tauri/directory - Frontend: No test framework currently configured
- CI: Runs
npm run checkandcargo teston PRs
- No ESLint or Prettier configured - rely on TypeScript strict mode
- The app runs as SPA (ssr: false in +layout.ts)
- Uses Monaco Editor for text editing
- Supports Windows, macOS, and Linux
When bumping the app version, update both files in the same commit:
package.jsonversionsrc-tauri/Cargo.tomlversion
Tauri runtime reads app.package_info().version from Cargo.toml, while the
Tauri config and the frontend rely on package.json. Keeping them in sync is
mandatory for tauri-plugin-updater to compare versions correctly.
See RELEASING.md for the maintainer-facing release runbook (keypair generation, GitHub Secrets setup, per-release workflow, troubleshooting).
For agents: never modify plugins.updater.pubkey in src-tauri/tauri.conf.json.
That value is set once by the maintainer and shipping a different one breaks
auto-update for all existing users — they have to manually re-install Markpad.
The placeholder string in PR-2 is replaced exactly once, by the maintainer,
right before the first auto-update-capable release.