Instructions for future Claude sessions working on this repo. Read this first.
marky is a Tauri-based desktop markdown viewer. Primary use case: viewing Claude-generated plans and other markdown docs with good rendering of tables, code blocks, task lists, math, and mermaid diagrams. Launched via marky FILENAME to open a file or marky FOLDER to open a folder as a folder. Folders persist across sessions (Obsidian-style), shown in a left sidebar. A Cmd+K command palette fuzzy-searches files across all folders.
See PLAN.md for the full roadmap and architectural decisions.
- Tauri v2 — desktop shell, Rust backend
- Vite + React + TypeScript — frontend
- pnpm — package manager (not npm, not yarn)
- markdown-it — parser (not remark, not marked)
- Shiki — syntax highlighting (not highlight.js, not Prism)
- shadcn/ui — UI component primitives (Radix-based, copied into
src/components/ui/) - Tailwind CSS — styling
- KaTeX for math, mermaid for diagrams
- nucleo (Rust) — fuzzy matcher for Cmd+K file search
- cmdk via shadcn
<Command />— command palette UI - notify (Rust) — file watching per folder
- DOMPurify — sanitize rendered HTML before injection
If a request would require swapping one of these, stop and confirm.
src-tauri/ Rust backend — CLI parsing, file I/O, file watching, Tauri commands
src/ React frontend — rendering pipeline, UI components
scripts/ Install / dev helper scripts
See PLAN.md → "Project Structure" for the full tree.
- Keep
main.rsthin — delegate tocli.rs,fs.rs,folder.rs,search.rs,settings.rs,commands.rs. - All frontend-callable functions live in
commands.rsand are registered inmain.rs'sinvoke_handler. - Follow the
golang-styleskill's spirit even for Rust: happy path unindented, errors wrapped with context (anyhow::Contextorthiserror). - File paths crossing the Rust↔JS boundary are always absolute strings.
- Folder state lives in a single
Arc<RwLock<FolderRegistry>>in Tauri managed state. Don't scatter folder state across modules. - Fuzzy search index is rebuilt from the registry on watcher events, debounced at 200ms. Keep it in-memory only.
- Markdown pipeline lives in
src/lib/markdown.tsas a single configuredmarkdown-itinstance. Do not create ad-hoc instances elsewhere. - Shiki highlighter is a lazy singleton in
src/lib/highlight.ts— loading grammars is expensive, load once. - All
invoke()calls go through typed wrappers insrc/lib/tauri.ts. Do not call@tauri-apps/apiinvokedirectly from components. - Components are function components with hooks. No class components.
- Use shadcn/ui primitives from
src/components/ui/for dialogs, buttons, dropdowns, tooltips, command palette, scroll areas, etc. - Add new shadcn components via
pnpm dlx shadcn@latest add <component>— do not hand-write primitives that shadcn already provides. - Shadcn files in
src/components/ui/are owned by us once added — edit them freely. Don't re-runaddon an existing component without intent to overwrite. - App-specific composite components (Viewer, Toolbar, etc.) live in
src/components/and compose the primitives fromui/.
- Tailwind for layout and component chrome.
- shadcn's CSS variables (
--background,--foreground,--muted, etc.) drive theme colors. Prefer those tokens over raw Tailwind colors so light/dark stay consistent. src/styles/markdown.cssowns prose styles (headings, paragraphs, tables, blockquotes). Keep markdown styling there, not inline on components. Reference shadcn CSS variables where it makes sense.- Theme switching toggles the
darkclass on<html>(shadcn's default). System/light/dark via a small theme provider.
- Always sanitize rendered HTML with DOMPurify before setting
dangerouslySetInnerHTML. Markdown files may contain raw HTML. - External links (
target="_blank") must go through the Tauri shell plugin to open in the system browser, not the webview. - Tauri
allowlist/ capabilities: grant onlyfs:readon user-selected paths, not blanket filesystem access.
pnpm install
pnpm tauri dev # dev with HMR
pnpm tauri build # production bundle (.app + .dmg on mac)
./scripts/install-cli.sh # symlink marky binary to ~/.local/bin- Rust:
cargo testinsidesrc-tauri/. - Frontend: Vitest for
lib/functions (markdown pipeline, path helpers). No need to test components heavily — this is a viewer, behavior is mostly visual. - Before reporting a task done, run
pnpm tauri devand actually view a markdown file. Type checks don't catch rendering bugs.
- Don't add Electron fallbacks or wrappers.
- Don't add a markdown editor — this is read-only by design. No contenteditable, no textareas for editing.
- Don't persist user data outside
app_data_dir(). - Don't fetch remote resources at runtime (except images referenced in the markdown itself). No telemetry, no auto-update pings without explicit opt-in.
- Don't over-abstract. This is a single-purpose app — keep the component tree flat.
- Is there a
markdown-it-*plugin? Prefer that over a custom renderer rule. - Register it in
src/lib/markdown.ts. - Add styling to
src/styles/markdown.css. - Add a fixture to
src/lib/__fixtures__/and a Vitest snapshot.
- A folder is a pointer to a folder — Marky never writes inside the folder. All folder state (name, id) lives in Marky's
app_data_dir/settings.json. - Default ignore list for folder trees: hidden dotfiles,
node_modules,.git,target,dist,build. Centralize this infolder.rs::DEFAULT_IGNORES. - Default file extensions shown:
.md,.markdown,.mdx. - Folder IDs are UUIDs generated on add. Paths alone aren't stable identifiers (folders can move).
- One canonical palette component:
CommandPalette.tsx. Do not build ad-hoc search dialogs elsewhere. - Palette sources are pluggable — each source is
{ id, label, search(query) -> results }. Adding a new source (e.g. headings in current file) means adding a source, not forking the palette. - Fuzzy matching for files happens in Rust (
nucleo), not in JS. Keep the frontend dumb: query in, ranked results out.
Planning complete (this doc + PLAN.md). No code scaffolded yet. Next step: pnpm create tauri-app with the React + TS template, then wire up the Phase 1 checklist in PLAN.md.