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.9 KiB
3.9 KiB
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: iflocalStorage.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.
- Always render
Part B — Git
Backend
git.ts: factorygitFor(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 addorset-url.POST /push/POST /pull— to origin, return{ ok, error? }.
routes/projects.ts:POST /:projectcallsgit.initafter seedingmain.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→ dispatchcommitFocusvia 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:
-
tinymist/preview/scrollSourcenotification:- Params:
{ filepath: string, start?: [number, number], end?: [number, number] } - Convert:
filepath→ relPath,start/endare 0-indexed [row, col] - Call
onJumpToSource({ filepath, start, end })
- Params:
-
window/showDocumentrequest:- Params:
{ uri: string, selection?: { start: {line, character}, end: {line, character} } } - Convert:
uri(file://) → relPath, selection is 0-indexed LspRange - Call
onJumpToSource(...), respond{ success: true }
- Params:
App State (app-state.tsx)
- Add
jumpTarget: { file: string; start?: [number, number]; end?: [number, number] } | nullto state. - In
startLsp, passonJumpToSourcecallback:- Convert absolute
filepath→ relPath (stripnew URL(projectUri).pathnameprefix) - Ignore if filepath is not under the project (e.g. imported package file)
- If
relPath !== state.openFile, dispatchopenFile - Dispatch
setJumpTarget({ file: relPath, start, end })
- Convert absolute
Editor (Editor.tsx)
- Effect deps:
[jumpTarget, model] - When
jumpTargetis truthy ANDmodelis 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 staleexeca(backend has simple-git, not execa). Update Architecture & Boundaries to reflect that frontend uses raw LSP, notmonaco-languageclient. Fix stalevscode-ws-jsonrpcreferences (frontend removed it; backend still uses it).