Files
typst-leaf/PLAN.md
T
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

81 lines
3.9 KiB
Markdown

# 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 `<div ref={vimStatusRef} />` (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).