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
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user