bfb4d8bef8
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
3.4 KiB
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 withnoUncheckedIndexedAccess. Entry:src/index.ts. Dev viatsx watch; build viatsc→dist/. Usesexpress+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/viteplugin (import with@import "tailwindcss"; there is notailwind.config.js). - Tauri v2 lives at
typst-leaf-frontend/src-tauri/. It shells out topnpm dev/pnpm buildand serves../dist. - Data plane:
ws://localhost:4000/lsp(LSP),ws://localhost:4000/preview(SVG stream).
Critical Constraints (do NOT violate)
- Never re-implement Typst/LSP logic in TypeScript. Spawn
tinymistas a child process and pipe WebSocket messages straight to its stdio. The backend is a thin proxy. - Never use PDF.js. The preview renders SVG streamed from tinymist (selectable text, instant diffs, no binary PDFs).
- 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.
- No polling for file or preview changes. Drive updates from LSP/WebSocket events only.
- Monaco LSP lifecycle is fragile. Always dispose the languageclient + WebSocket on component unmount to avoid leaking duplicate socket connections.
- 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. - 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 withzod. - Prefer
async/awaitover raw Promises. - Comment complex logic around WebSocket message passing and process spawning.