# Next Chunk: Git + Vim + SyncTeX (Core VISION) ## Part A — Vim Mode (frontend-only) **Goal:** Off-by-default vim toggle, persisted to localStorage. - `Editor.tsx`: - Always render `
` (empty when vim is off, monaco-vim populates it when on). - On `handleMount`: if `localStorage.getItem("typst-leaf.vim") === "true"`, `initVimMode(editor, vimStatusRef.current)`. - Add "Vim" toggle button in the editor toolbar (emerald when active). - On toggle: persist to localStorage; init or dispose accordingly. - Store `initVimMode`'s returned controller in a ref for disposal on unmount/toggle-off. ## Part B — Git ### Backend - `git.ts`: factory `gitFor(project: string)` → `simpleGit(projectDir(project))`. - `routes/git.ts` (mounted at `/api/projects/:project/git`): - `POST /init` — `git init`, set default identity from env, initial commit, `.gitignore`. - `GET /status` — branch, ahead/behind, staged/unstaged, clean. - `GET /log?limit=20` — commit history. - `POST /commit` — body `{ message, all?, identity? }`. Uses `-c user.name=... -c user.email=...` per-commit flags (not repo config). - `PUT /remote` — body `{ url }` — `git remote add` or `set-url`. - `POST /push` / `POST /pull` — to origin, return `{ ok, error? }`. - `routes/projects.ts`: `POST /:project` calls `git.init` after seeding `main.typ`. - `index.ts`: mount gitRouter. ### Frontend - `api.ts`: gitInit, gitStatus, gitLog, gitCommit, gitPush, gitPull, setRemote. - `types.ts`: FileStatus, CommitInfo, GitStatus. - `GitPanel.tsx` (sidebar, below FileTree): - Branch name, ahead/behind badges, unstaged file count. - Inline commit: message input + Commit button (Enter to submit). - Push/Pull buttons; prompts for remote URL if none configured. - Refreshes on project open + after saves + after commits. - `SettingsPanel.tsx` (gear icon in sidebar header toggles panel): - Git Author Name, Git Author Email inputs. - Persisted to `localStorage["typst-leaf.git-identity"]`. - Sent with every commit as `identity`. - `Editor.tsx`: `CtrlCmd+Shift+KeyS` → dispatch `commitFocus` via app state. - `app-state.tsx`: `gitStatus`, `gitVersion` (incremented on save to trigger refresh), `commitFocus` (incremented on shortcut). ## Part C — SyncTeX (Preview → Editor) ### LSP Client (`lsp.ts`) Two LSP message paths from tinymist, handle both: 1. **`tinymist/preview/scrollSource` notification**: - Params: `{ filepath: string, start?: [number, number], end?: [number, number] }` - Convert: `filepath` → relPath, `start/end` are 0-indexed [row, col] - Call `onJumpToSource({ filepath, start, end })` 2. **`window/showDocument` request**: - Params: `{ uri: string, selection?: { start: {line, character}, end: {line, character} } }` - Convert: `uri` (file://) → relPath, selection is 0-indexed LspRange - Call `onJumpToSource(...)`, respond `{ success: true }` ### App State (`app-state.tsx`) - Add `jumpTarget: { file: string; start?: [number, number]; end?: [number, number] } | null` to state. - In `startLsp`, pass `onJumpToSource` callback: 1. Convert absolute `filepath` → relPath (strip `new URL(projectUri).pathname` prefix) 2. **Ignore if filepath is not under the project** (e.g. imported package file) 3. If `relPath !== state.openFile`, dispatch `openFile` 4. Dispatch `setJumpTarget({ file: relPath, start, end })` ### Editor (`Editor.tsx`) - Effect deps: `[jumpTarget, model]` - When `jumpTarget` is truthy AND `model` is the correct file: - Convert 0-indexed [row, col] → Monaco 1-indexed (line = row + 1, column = col + 1) - `editor.setSelection(range)`, `editor.revealRangeInCenter(range)`, focus editor - Dispatch `clearJumpTarget` ## Part D — Doc Cleanup - `AGENTS.md`: Remove stale `execa` (backend has simple-git, not execa). Update Architecture & Boundaries to reflect that frontend uses raw LSP, not `monaco-languageclient`. Fix stale `vscode-ws-jsonrpc` references (frontend removed it; backend still uses it).