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:
2026-07-02 00:35:32 +05:30
parent 061f95ddee
commit bfb4d8bef8
16 changed files with 1123 additions and 45 deletions
+5 -1
View File
@@ -1,8 +1,10 @@
import { AppProvider, useApp } from "./app-state";
import { EditorView } from "./components/Editor";
import { FileTree } from "./components/FileTree";
import { GitPanel } from "./components/GitPanel";
import { Preview } from "./components/Preview";
import { ProjectPicker } from "./components/ProjectPicker";
import { SettingsPanel } from "./components/SettingsPanel";
function Shell() {
const { state, lsp } = useApp();
@@ -11,7 +13,8 @@ function Shell() {
<aside className="w-60 border-r border-zinc-200 flex flex-col bg-zinc-50">
<div className="px-3 py-2 border-b border-zinc-200 text-sm font-semibold flex items-center gap-2">
<span>typst-leaf</span>
<span className="ml-auto text-xs font-normal text-zinc-400">
<span className="ml-auto flex items-center gap-2 text-xs font-normal text-zinc-400">
<SettingsPanel />
{lsp.status === "ready"
? "●"
: lsp.status === "connecting"
@@ -25,6 +28,7 @@ function Shell() {
<div className="flex-1 overflow-auto">
<FileTree />
</div>
<GitPanel />
{state.error && (
<div className="p-2 text-xs text-red-500 border-t border-zinc-200">
{state.error}