feat: pdf support#2166
Conversation
Greptile SummaryThis PR adds first-class PDF support across both local and SSH workspaces: PDFs can now be opened as file tabs and compared side-by-side in the diff view. It also replaces
Confidence Score: 4/5Safe to merge after adding error handlers to both the chokidar and native fs.watch watchers in local-fs.ts Both the chokidar watcher (Linux path) and the native fs.watch watcher (macOS/Windows path) are missing 'error' event listeners. Since both FSWatcher types extend Node.js EventEmitter, an unhandled 'error' event — triggered by conditions like inotify limits, permission changes, or the watched directory being deleted — would throw an uncaught exception in the Electron main process. The rest of the PR is well-constructed: the token-based URL scheme addresses the earlier memory-leak concern, the readSftpFully loop fixes the partial-read issue, and the diff view's reactive query keying correctly handles revision changes. src/main/core/fs/impl/local-fs.ts — both the chokidar branch (line 802) and the fs.watch branch (line 838) need an 'error' event handler added before merge
|
| Filename | Overview |
|---|---|
| src/main/core/fs/impl/local-fs.ts | Replaces parcel/watcher with a chokidar (Linux) / native fs.watch (macOS+Win) hybrid and adds readPdf; both new watchers are missing an 'error' event handler that could crash the main process |
| src/main/app/protocol.ts | Adds token-based workspace file URL generation with a 30s TTL timer for auto-cleanup; correctly addresses the previously reported unbounded-map leak |
| src/main/core/fs/impl/ssh-fs.ts | Adds readSftpFully loop helper (fixing the partial-read issue) and readPdf returning base64 data URLs for SSH workspaces |
| src/main/core/git/impl/git-service.ts | Refactors _readImageBlob into a shared _readPreviewBlob helper and adds getPdfAtRef/getPdfAtIndex; refactoring is clean and backward-compatible |
| src/renderer/features/tasks/diff-view/main-panel/diff-file-renderer.tsx | Adds PdfDiffView with side-by-side panels, reactive React Query fetching, and transient retry logic; loadPdfFromDisk misclassifies 'too large' errors as the generic git-error reason |
| src/renderer/features/tasks/editor/stores/file-model-lifecycle-store.ts | Extends preview loading to PDFs with a pendingPreviewLoads deduplication guard and a 30s timeout; logic is correct |
| src/renderer/lib/editor/pdf-renderer.tsx | New PDF renderer using the browser's built-in object viewer with loading/error states; straightforward and correct |
| src/shared/git.ts | Adds PdfReadResult type mirroring ImageReadResult; clean addition with no issues |
Sequence Diagram
sequenceDiagram
participant R as Renderer
participant IPC as IPC rpc
participant FS as LocalFS or SshFS
participant P as protocol.ts
participant OBJ as object element
R->>IPC: rpc.fs.readPdf(projectId, workspaceId, path)
IPC->>FS: readPdf(path)
alt Local filesystem
FS->>FS: stat fullPath size and existence check
FS->>P: createWorkspaceFileUrl(fullPath)
P-->>FS: app token URL with 30s TTL
FS-->>IPC: success true fileUrl mimeType size
else SSH filesystem
FS->>FS: sftp.open and fstat
FS->>FS: readSftpFully loop handles partial reads
FS-->>IPC: success true dataUrl base64 mimeType size
end
IPC-->>R: result
R->>OBJ: data set to fileUrl or dataUrl
OBJ-->>R: PDF rendered by Chromium built-in viewer
Note over R,P: Diff view disk group uses React Query staleTime 25s to trigger background refetch before 30s TTL expires
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
src/main/core/fs/impl/local-fs.ts:802-835
**Missing error handler on chokidar watcher**
`chokidar`'s `FSWatcher` extends Node.js's `EventEmitter`. If it emits an `'error'` event — which it does for conditions like permission-denied, inotify limit exceeded, or the watched path being deleted while watching — and no `'error'` listener is attached, Node.js immediately re-throws the error as an uncaught exception. In Electron's main process, this would crash the process.
The same gap exists in the `fs.watch` path below (line 838): `FSWatcher` is also an `EventEmitter` that emits `'error'` for similar conditions (e.g., `ENOENT` when the directory disappears). The previous `parcel/watcher` used a callback pattern where errors were delivered to the subscriber callback; that contract is not preserved with the new event-based watchers, so adding an `'error'` handler (e.g., `watcher.on('error', (err) => log.error('watcher error', err))`) is required for both branches.
### Issue 2 of 2
src/renderer/features/tasks/diff-view/main-panel/diff-file-renderer.tsx:313-317
**"Too large" errors incorrectly map to the generic `'git-error'` reason**
When `rpc.fs.readPdf` returns `{ success: false, error: 'PDF too large: ...' }`, this branch falls through the `/not found/i` check and returns `{ status: 'unavailable', reason: 'git-error' }`. `pdfUnavailableMessage` then renders `'Preview unavailable'` rather than `'Preview unavailable — PDF is too large'`. Adding a `'too-large'` check here lets the correct message surface to the user.
```suggestion
if (!pdf?.success) {
const error = pdf?.error ?? '';
if (/not found/i.test(error)) return { status: 'missing' };
if (/too large/i.test(error)) return { status: 'unavailable', reason: 'too-large' };
return { status: 'unavailable', reason: 'git-error' };
}
```
Reviews (4): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile
|
would be cool if you could also annotate the content:) just a thought. Will review soon! |
|
like annotating as in like sending that also to an agent? |
|
yes |
|
should i do this before merge? yea or? |
# Conflicts: # src/renderer/features/tasks/diff-view/main-panel/diff-file-renderer.tsx # src/renderer/features/tasks/tabs/file-tab-store.ts
|
unf. outdated - @janburzinski |


No description provided.