008dab1cf9
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.
201 lines
8.1 KiB
Markdown
201 lines
8.1 KiB
Markdown
# 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 + `.git` remain the source of truth.
|
|
- No polling for file or preview changes.
|
|
- Backend stays a thin proxy. No Typst or LSP reimplementation in TypeScript.
|
|
- `tinymist` remains 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): string`
|
|
- `encodeVirtualPathForFileUri(path: string): string`
|
|
- `relativePathFromProject(projectUri: string, filepath: string): string | null`
|
|
|
|
**State changes** (`app-state.tsx`)
|
|
- `jumpTarget: { id: number; file: string; start?: [number,number]; end?: [number,number] } | null`
|
|
- `setJumpTarget` action carries an `id`; reducer increments counter and stamps.
|
|
- New `pushNotice` action for transient messages ("Preview source outside project").
|
|
|
|
**LSP hardening** (`lsp.ts`)
|
|
- Validate `tinymist/preview/scrollSource` payload strictly: `filepath` string, optional numeric `start/end`.
|
|
- Forward only well-formed paths to `onJumpToSource`.
|
|
|
|
**Editor** (`Editor.tsx`)
|
|
- Use shared path utility.
|
|
- Compare URIs via `monaco.Uri.equals` after parsing both sides.
|
|
- Dedupe by `jump.id`, not object identity.
|
|
- Keep the existing `ResizeObserver` and `editor.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` (extend `tailwindcss` import; nothing else without need)
|
|
- New components:
|
|
- `TopBar.tsx`
|
|
- `StatusBar.tsx`
|
|
- `Sidebar.tsx`
|
|
- `PanelSection.tsx`
|
|
- `ActivityBar.tsx`
|
|
- `ResizableSplit.tsx`
|
|
- `ToastHost.tsx`
|
|
- `EmptyState.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-50` for chrome, `bg-white` for editor surface, `bg-emerald-600` for primary actions, `border-zinc-200` for panel dividers, `text-zinc-500` for 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 `StatusBar` hints later if time.
|
|
|
|
## Phase 3 — Resizable Workspace and Preview UX
|
|
|
|
- Build `ResizableSplit.tsx` (pointer-driven, double-click reset, persisted ratio).
|
|
- Persist `typst-leaf.split` in `localStorage`.
|
|
- Add `PreviewToolbar` on 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/file`
|
|
- `DELETE /api/projects/:project/file?path=…`
|
|
- `PATCH /api/projects/:project/file`
|
|
- `POST /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 `setTree` already exists. Add `bumpFileTree` counter or reuse `setProjects` invalidation by re-fetching tree on a `treeVersion`.
|
|
|
|
## 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=1` later is optional.
|
|
|
|
## Phase 6 — Git Workflow Upgrade
|
|
|
|
Backend
|
|
- `GET /api/projects/:project/git/diff` returning unified diffs (later; not blocking if skipped).
|
|
- Optional `POST /api/projects/:project/git/stage` for partial staging (later).
|
|
|
|
Frontend
|
|
- `GitPanel.tsx` becomes 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.tsx` shows 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 `FileStatusInfo` carries `A|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)
|
|
- `setFileDirty`
|
|
- `setSaving`
|
|
- `pushNotice` / `dismissNotice`
|
|
- `setSplitRatio` (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; soften `monaco-languageclient` claim.
|
|
- `typst-leaf-frontend/README.md`: replace Tauri-template text.
|
|
- `PLAN.md`: replace with this plan at commit time. Keep `DESIGN.md` separate and referenced.
|
|
|
|
## Implementation Order
|
|
|
|
1. Shared path utilities + jump IDs.
|
|
2. SyncTeX hardening (rely on shared utils, not yet aesthetic).
|
|
3. UI shell rehaul (TopBar, Sidebar, ResizableSplit, StatusBar, ToastHost).
|
|
4. File CRUD backend + frontend integration, FileTree rebuild.
|
|
5. Git workflow UI rebuild + badges in tree.
|
|
6. Preview toolbar and reload.
|
|
7. Doc cleanup.
|
|
8. Verification:
|
|
- `pnpm -r typecheck`
|
|
- `pnpm --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.
|