Files
nav.sikand bfb4d8bef8 feat: Git workflow, Vim mode, and preview→editor SyncTeX
Add three core VISION features on top of the barebones MVP:

Git workflow (backend + frontend):
- Backend: simple-git wrapper with per-project repo isolation, init
  (idempotent .gitignore reconciliation, handles empty repos), status,
  log, commit (per-commit identity via -c flags), remote, push, pull
- gitFor() now refuses to run without the project's own .git, preventing
  accidental parent-repo detection when projects/ lives inside the
  typst-leaf worktree
- Frontend: GitPanel (branch, ahead/behind, inline commit, push/pull,
  remote prompt, collapsible log), SettingsPanel (git identity in
  localStorage), Ctrl+Shift+S focuses commit input
- Status/log refresh driven by gitVersion bumps (no polling) with
  cancellation guards across project switches
- Auto-init on status failure for pre-existing projects

Vim mode:
- Off-by-default toggle persisted to localStorage
- Dynamic monaco-vim import, status bar div, StrictMode-safe lifecycle
  via editorReady reset on unmount

SyncTeX (preview → editor):
- Handle tinymist/preview/scrollSource notification and
  window/showDocument request
- Cross-platform path normalization (Windows drive letters, native
  backslash paths, URL-decoded paths)
- Editor jump effect with Monaco selection/reveal/focus, clears on
  completion

Robustness fixes across review cycles:
- Cancellation guards on git status/log effects to prevent stale writes
- bumpGitVersion after auto-init so log viewer updates
- jsonOrThrow extracts structured backend errors, handles empty bodies
- SettingsPanel/GitPanel validate localStorage identity shape
- GitPanel clears log state on project switch
- File path URI encoding preserves path separators (segment-wise encode)
- tsconfig lib bumped to ES2021 for String.replaceAll

AGENTS.md: removed stale execa/monaco-languageclient references

PLAN.md: implementation plan for this chunk
2026-07-02 00:35:32 +05:30

3.4 KiB

AGENTS.md

Guidelines for AI coding assistants working on typst-leaf.

Project Context

Self-hosted web IDE for the Typst typesetting language — a self-hosted "Overleaf" alternative. Built on "Git as the Database": projects are plain folders with .git, no external database. Architecture and philosophy are detailed in VISION.md.

Commands

This is a pnpm workspace (see pnpm-workspace.yaml). Install once from the repo root:

pnpm install                          # installs both backend and frontend
pnpm -r typecheck                     # typecheck ALL workspaces (run before finishing a task)
pnpm --filter typst-leaf-backend dev  # backend dev server on http://localhost:4000
pnpm --filter typst-leaf-frontend dev # frontend dev server (Vite)
pnpm --filter typst-leaf-frontend tauri dev  # Tauri desktop wrapper (runs Vite first)

No test framework is configured — the test scripts are placeholders. Do not assume one exists.

Architecture & Boundaries

typst-leaf-backend/     Node.js/TS — stateless orchestration: spawns tinymist, proxies WebSockets, REST for files + git
typst-leaf-frontend/    React (Vite) + Monaco — the IDE SPA; also contains src-tauri/ for the desktop build
  • Backend (typst-leaf-backend/): ESM ("type": "module"), strict TS with noUncheckedIndexedAccess. Entry: src/index.ts. Dev via tsx watch; build via tscdist/. Uses express + ws + simple-git + zod.
  • Frontend (typst-leaf-frontend/): React 19 + Vite. Monaco Editor wired to LSP via a raw JSON-RPC WebSocket client (src/lsp.ts). Styling is Tailwind v4 via the @tailwindcss/vite plugin (import with @import "tailwindcss"; there is no tailwind.config.js).
  • Tauri v2 lives at typst-leaf-frontend/src-tauri/. It shells out to pnpm dev/pnpm build and serves ../dist.
  • Data plane: ws://localhost:4000/lsp (LSP), ws://localhost:4000/preview (SVG stream).

Critical Constraints (do NOT violate)

  1. Never re-implement Typst/LSP logic in TypeScript. Spawn tinymist as a child process and pipe WebSocket messages straight to its stdio. The backend is a thin proxy.
  2. Never use PDF.js. The preview renders SVG streamed from tinymist (selectable text, instant diffs, no binary PDFs).
  3. Path duality: the frontend sees virtual paths; the backend sees absolute OS paths under the projects root. Keep the mapping explicit — this is a frequent source of bugs.
  4. No polling for file or preview changes. Drive updates from LSP/WebSocket events only.
  5. Monaco LSP lifecycle is fragile. Always dispose the languageclient + WebSocket on component unmount to avoid leaking duplicate socket connections.
  6. Browser-first. The app must run in a generic browser. Guard any Tauri-only API behind a window.__TAURI__ check; do not rely on it exclusively.
  7. No databases. State = filesystem; history = .git. Collaboration is Git (commit/push/pull in the UI), not OT/CRDTs.

Port Gotcha

The frontend dev server is port 1420 (strictPort: true, mandated by Tauri) — not 3000 as README.md states. Backend is port 4000. README.md is stale on this point; trust vite.config.ts.

Code Style

  • TypeScript strict mode in both packages. No any. Validate at API boundaries with zod.
  • Prefer async/await over raw Promises.
  • Comment complex logic around WebSocket message passing and process spawning.