This is the next major chunk on top of the barebones MVP. It implements
the IDE shell, resizable editor/preview workspace, file CRUD, Git workflow
UI upgrade, SyncTeX/LSP reliability hardening, and documentation updates.
Highlights:
* New shared path utilities (typst-leaf-frontend/src/path-utils.ts):
normalizeFsPath, encodeVirtualPathForFileUri, relativePathFromProject.
* SyncTeX reliability:
* Jump targets now carry stable numeric IDs for deduplication.
* tinymist/preview/scrollSource and window/showDocument payloads are
validated and normalized through shared path utilities.
* External preview paths are reported via a toast instead of silent drop.
* Editor uses monaco.Uri.equals for URI comparison.
* IDE shell rehaul:
* New components: TopBar, StatusBar, PanelSection, ResizableSplit,
ToastHost, EmptyState.
* App.tsx now uses a layered layout: top bar, sidebar tabs
(Files/Git/Settings), resizable main workspace, status bar, toasts.
* Sidebar tabs replace the old inline Git/settings panels.
* Resizable workspace:
* Pointer-driven resizable split between editor and preview.
* Split ratio persisted to localStorage (typst-leaf.split).
* Double-click splitter resets to 55%.
* onResize prop dispatches typst-leaf:layout so Monaco re-layouts.
* File management:
* Backend CRUD: POST /file, DELETE /file, PATCH /file, POST /folder.
* Frontend API wrappers and inline FileTree create/rename/delete.
* Backend tree endpoint returns empty directories in a folders array.
* Rename protects the .typ extension and rejects overwriting existing
files atomically on POSIX.
* Newly created files are opened automatically.
* Dirty file badges in the tree.
* Git workflow upgrade:
* GitPanel now shows changed files grouped by staged/unstaged, deduped
by path, with per-file status icons.
* Click a changed file to open it.
* Commit disabled when clean or no message.
* Push/pull with success/error toasts.
* FileTree shows dirty badges from git status.
* Preview UX:
* Preview toolbar with status chip, reload button, and SyncTeX hint.
* Preview hidden/marked as "open a file" when no file is open.
* State/UX infrastructure (app-state.tsx):
* activePanel, fileDirty, saving, notice (toast), vimOn, treeVersion,
folders, and stable jumpTarget.id.
* focusCommit switches to the Git panel.
* selectProject resets more state to avoid stale data.
* Editor improvements:
* Monaco model disposed on unmount so LSP sees didClose.
* mountedRef survives React StrictMode remounts.
* Global save/layout events bridge TopBar and ResizableSplit to the
editor instance.
* Vim mode state moved to global state/localStorage.
* Removed redundant automaticLayout and inline Save button.
* Documentation:
* Added DESIGN.md with the full UI aesthetic guide.
* Updated PLAN.md, README.md, VISION.md, and the frontend README.
Verification:
* pnpm -r typecheck passes.
* pnpm --filter typst-leaf-frontend build passes.
8.1 KiB
Next Chunk: IDE Shell, File Management, UI Rehaul, Reliability, Git Workflow
This chunk is the next deliverable on top of the bfb4d8b commit. It picks options 1, 2, and 3: IDE shell + file management + UI rehaul, reliability and LSP/Preview hardening, and Git workflow upgrade. Direction and aesthetic decisions live in DESIGN.md — every UI decision below should read against that doc.
Guiding Constraints
- Tailwind only, light theme only, no dark variant.
- Browser-first; Tauri APIs only behind
window.__TAURI__guards. - No databases; filesystem +
.gitremain the source of truth. - No polling for file or preview changes.
- Backend stays a thin proxy. No Typst or LSP reimplementation in TypeScript.
tinymistremains the LSP/preview source of truth.- Keep the raw JSON-RPC LSP client. Do not reintroduce
monaco-languageclient. - Strict TypeScript with zod at API boundaries.
Aesthetic Direction (see DESIGN.md)
- Document-editor tone: warm paper background, subtle emerald accents, no marketing gradients.
- Dense-but-readable developer layout. System sans for chrome, monospace for paths/git.
- Rounded corners and borders restrained to "panel" and "card"; default to
rounded-md. - Status indicators must read clearly (label + color, never color alone).
- Splitter, focus rings, etc. all designed in DESIGN.md; the implementation must match.
Phase 1 — Path Utilities and Jump Reliability
Move path/jump helpers into a single module so all callers use the same normalization rules.
New file
typst-leaf-frontend/src/path-utils.ts:normalizeFsPath(input: string): stringencodeVirtualPathForFileUri(path: string): stringrelativePathFromProject(projectUri: string, filepath: string): string | null
State changes (app-state.tsx)
jumpTarget: { id: number; file: string; start?: [number,number]; end?: [number,number] } | nullsetJumpTargetaction carries anid; reducer increments counter and stamps.- New
pushNoticeaction for transient messages ("Preview source outside project").
LSP hardening (lsp.ts)
- Validate
tinymist/preview/scrollSourcepayload strictly:filepathstring, optional numericstart/end. - Forward only well-formed paths to
onJumpToSource.
Editor (Editor.tsx)
- Use shared path utility.
- Compare URIs via
monaco.Uri.equalsafter parsing both sides. - Dedupe by
jump.id, not object identity. - Keep the existing
ResizeObserverandeditor.layout()call before reveal.
Verification
- External preview path ignored with non-blocking message.
- Numeric start/end coercion.
- Rapid clicks keep latest, no swallowed targets.
Phase 2 — UI Foundation (Rehaul)
Replace the current sparse shell with the layered "IDE" layout in DESIGN.md.
Files
typst-leaf-frontend/src/App.tsx(restructure)typst-leaf-frontend/src/index.css(extendtailwindcssimport; nothing else without need)- New components:
TopBar.tsxStatusBar.tsxSidebar.tsxPanelSection.tsxActivityBar.tsxResizableSplit.tsxToastHost.tsxEmptyState.tsx
Layout
- Top bar: project switcher, current file/dirty indicator, save, LSP/preview/git chips, settings.
- Sidebar tabs: Files / Git / Settings.
- Main: resizable editor / preview split.
- Status bar: branch, clean/dirty, LSP, preview, vim state, cursor (optional).
Aesthetic tokens (mirror DESIGN.md)
bg-stone-50for chrome,bg-whitefor editor surface,bg-emerald-600for primary actions,border-zinc-200for panel dividers,text-zinc-500for muted metadata.- Tailwind reference snippets are kept in DESIGN.md.
QOL
- Toasts for save/commit/push/pull, configured via
pushNotice. - Clear empty states ("Select a project", "Open a file", "No changes to commit").
- Keyboard shortcuts surfaced in
StatusBarhints later if time.
Phase 3 — Resizable Workspace and Preview UX
- Build
ResizableSplit.tsx(pointer-driven, double-click reset, persisted ratio). - Persist
typst-leaf.splitinlocalStorage. - Add
PreviewToolbaron top of iframe: status chip, reload button, "Click to jump" hint. - During drag and on drag-end, call
editor.layout()so Monaco keeps accurate viewports.
Phase 4 — File Management
Make projects usable from the UI.
Backend
typst-leaf-backend/src/routes/files.ts:
POST /api/projects/:project/fileDELETE /api/projects/:project/file?path=…PATCH /api/projects/:project/filePOST /api/projects/:project/folder
All paths through resolveFile/relPathSchema. zod validation stays.
Frontend
api.ts:
createFile,deleteFile,renameFile,createFolder.
types.ts:
- Add request types.
FileTree.tsx:
- Group tree view; folders from existing file paths.
- Inline create/rename/delete actions; confirm destructive deletes.
- After mutation: refresh tree, dispatch
bumpGitVersion, open new file or close deleted open file (with model disposal).
app-state.tsx:
- Action
setTreealready exists. AddbumpFileTreecounter or reusesetProjectsinvalidation by re-fetching tree on atreeVersion.
Phase 5 — SyncTeX/LSP Reliability Hardening
(See Phase 1.) This phase covers the user-visible behavior of the jump path:
- Stable IDs prevent dropped or duplicated jumps.
- External path is announced via toast instead of silently dropping.
- Vue of preview-after-resize: drag resize triggers
editor.layout(). Preview iframe reload button re-syncs. - A small dev-only "Diagnostics" panel hidden behind
?debug=1later is optional.
Phase 6 — Git Workflow Upgrade
Backend
GET /api/projects/:project/git/diffreturning unified diffs (later; not blocking if skipped).- Optional
POST /api/projects/:project/git/stagefor partial staging (later).
Frontend
GitPanel.tsxbecomes a workflow panel:- Branch + ahead/behind + clean state
- Changed files list grouped by staged/unstaged/untracked (icons per status)
- Commit composer
- Push/Pull controls
- Remote inline input
- Recent commits list (keep existing)
FileTree.tsxshows dirty file badges.- Toasts on success/failure: "Committed abc1234", "Pushed to origin/main", etc.
- Disable commit when clean; message should explain why.
types.ts:
- Confirm
FileStatusInfocarriesA|M|D|?(already does).
Phase 7 — State and UX Infrastructure
Keep state in app-state.tsx (no Zustand yet). Add UI actions:
setActivePanel(files | git | settings)setFileDirtysetSavingpushNotice/dismissNoticesetSplitRatio(effect persists via localStorage directly; not strict state)
Toast component lives once at App.tsx, reads notices via context.
Phase 8 — Doc and Metadata Cleanup
README.md: port 1420, raw LSP client, preview-via-LSP-and-proxy model.VISION.md: note raw LSP client; softenmonaco-languageclientclaim.typst-leaf-frontend/README.md: replace Tauri-template text.PLAN.md: replace with this plan at commit time. KeepDESIGN.mdseparate and referenced.
Implementation Order
- Shared path utilities + jump IDs.
- SyncTeX hardening (rely on shared utils, not yet aesthetic).
- UI shell rehaul (TopBar, Sidebar, ResizableSplit, StatusBar, ToastHost).
- File CRUD backend + frontend integration, FileTree rebuild.
- Git workflow UI rebuild + badges in tree.
- Preview toolbar and reload.
- Doc cleanup.
- Verification:
pnpm -r typecheckpnpm --filter typst-leaf-frontend build- Manual checks: file CRUD, resize-during-jump, Git flow, external preview path ignored.
Risks
- File URI/path encoding is still the highest-risk area; reuse existing helpers in
Editor.tsx. - Rename/delete of currently open file must dispose Monaco model.
- Git status can stale if a file mutation does not bump
gitVersion. - Resizable panes must call
layout()during and after drag. - Preview iframe behavior is owned by typst-preview; do not over-customize it.
Scope Boundary
In:
- IDE shell rehaul, resizable split, file CRUD, Git workflow UI upgrade, SyncTeX reliability, toasts, status bar.
Out:
- Per-file staging workflow.
- Rich diff viewer.
- Drag/drop reordering, multi-tab editor, collaborative cursors.
- Tauri native file picker.