Conversation
651ba39 to
8bbe8bc
Compare
🔍 Multi-Reviewer Code Review — PR #507 (Re-review #3)CodeMirror 6 Side-by-Side Diff Editor
Previous Findings — Status (Re-review #2 → #3)
Details on FixesFinding 1 ✅: Finding 2 ✅: Path check now uses: var normalizedWorkDir = Path.GetFullPath(workDir)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
if (!filePath.StartsWith(normalizedWorkDir, StringComparison.OrdinalIgnoreCase))Applied consistently in both read and write paths. Confirmed by all 3 reviewers. Finding 3 Findings 4–6 ✅: All confirmed fixed by all 3 reviewers. New Findings (this re-review)🟡 NEW-1 — Inline chat diffs missing
|
| Finding | Reviewer | Reason |
|---|---|---|
_applySaveStatus not cleared on RawDiff change |
1/3 | Cosmetic edge case — status auto-clears in 2s and Apply button isn't rendered until editor mode re-entered |
_prDiffCacheKey length-based collision |
1/3 | Theoretical — same PR, same diff length, different content is extremely rare |
BeginLineComment spurious async / CS1998 |
1/3 | Valid nit but compiler warning only |
| Path-traversal-blocked Apply shows "✅ Saved" | 1/3 | HandleApplyEditAsync returns without invoking callback on block — caller doesn't set "saved". Verified as false positive. |
ReconstructOriginal gap-fill uses wrong file version |
1/3 | Display-only (original side read-only), no data path affected |
Recommendation: ⚠️ Request Changes (minor)
5 of 6 previous findings are fully fixed — excellent iteration! 🎉 The two 🔴 CRITICAL issues (data loss + path traversal) are resolved.
Two 🟡 MODERATE items remain:
- NEW-1: Disable Apply (or pass
FileContentProvider) on inline chat diffs to prevent the same data-loss bug on that code path - NEW-2 / Finding 3 remainder: Pass
OldFileNameto JS for editor-view renamed-file comments
Neither is a show-stopper, but both are real user-facing bugs that will be hit in normal usage. A quick follow-up commit addressing these two would earn a ✅ Approve.
501599b to
71986f7
Compare
Integrates CodeMirror 6 as an alternative diff viewer alongside the existing table-based DiffView. Each file in a diff now shows Table/Editor toggle buttons in the file header. Components: - CodeMirror 6 bundle (660KB minified) with C#, JS, CSS, HTML, JSON, XML, Python, Markdown language support - Custom dark theme matching PolyPilot's color palette - MergeView for side-by-side diff with collapsed unchanged regions - Line numbers, bracket matching, fold gutters, Ctrl+F search - DiffParser.ReconstructOriginal/Modified for extracting both sides Also fixes pre-existing ChangeModelAsync test compilation errors (missing named parameter for reasoningEffort). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use @codemirror/legacy-modes pre-built csharp export instead of custom clike config (simpler, maintained upstream) - Add shell/bash/zsh syntax highlighting - Move CodeMirror DOM styles to global app.css (Blazor scoped CSS can't reach CodeMirror's dynamically created elements) - Hide merge-view revert buttons (read-only view) - Add merge-spacer gutter styling Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace async void SetViewMode with sync method + _pendingEditorInit set. CodeMirror initialization now happens in OnAfterRenderAsync (guaranteed DOM availability) instead of Task.Delay(50) (race-prone). - Add 4 edge case tests: deleted file, empty hunks, multi-hunk, context-only diffs for ReconstructOriginal/ReconstructModified. - Tests: 3136/3136 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-hunk gaps - #1 (CRITICAL): Snapshot CM instance IDs before clearing dictionaries in OnParametersSet, preventing InvalidOperationException from enumerator invalidation and ensuring all JS instances are properly disposed - #2 (CRITICAL): Add generation counter to InitCodeMirrorForFile; stale completions (from a previous diff) are detected and disposed immediately - #3: Double-init guard — skip InitCodeMirrorForFile if _cmInstances already contains an entry for the fileIdx - #4: DisposeCmInstance replaced with snapshot-then-remove pattern; _pendingEditorInit cleared on Table toggle to prevent stale inits - #5: ReconstructOriginal/Modified now insert blank placeholder lines for inter-hunk gaps to preserve correct line numbering in CM MergeView - #6: Add SRI integrity hash (sha384) to codemirror-bundle.js script tag - #7: Replace O(n²) Files.IndexOf(file) with indexed for loop - #8: All catch blocks now log to Console.Error with [DiffView] prefix - NEW-1: Post-loop Clear() race eliminated — DisposeJsInstancesByIdAsync works on snapshotted arrays, never touches _cmInstances dictionary - Added _disposed guard to DisposeAsync for safe double-dispose Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add thorough test coverage for the CodeMirror diff editor feature: - Inter-hunk gap placeholder tests: verify ReconstructOriginal/Modified insert correct blank lines between distant hunks (review fix #5) - PairLines side-by-side pairing: context-only, matched pairs, uneven remove/add counts, pure additions/deletions, mixed interleaving, empty hunks, multiple independent change blocks - Multi-file mixed types: new + deleted + modified + renamed in one diff - Edge cases: CRLF handling, empty added/removed lines, hunk headers with and without function names, line number sequencing, insertions causing offset, multiple hunks resetting line numbers - ShouldRenderDiffView integration: Read/view tool filtering, null tool - IsPlainTextViewTool case insensitivity - Reconstruction round-trip integrity: single-line, multi-hunk gaps with exact line counts, gap adjustment for added lines - Real-world patterns: binary file notices, index lines, three-hunk diffs, hunk headers without comma Total DiffParser tests: 67 (28 existing + 39 new), all passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ShouldRenderDiffView already validates the content is a parseable unified diff. The extra Parse + Count > 0 guard in the render body was always true when _hasDiffOutput was set, making the else branch (fallback <pre>) dead code. DiffView internally parses via OnParametersSet, so the removed call was a wasted parse per render. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a desktop Review button for linked PRs that opens a side-by-side diff panel, fetches gh pr diff output, and renders it with the DiffView file picker/editor toggle. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
5a8b97f to
8fb9e19
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…false) The merge view (diff editor) had both EditorState.readOnly.of(true) and EditorView.editable.of(false). The editable(false) sets contentEditable=false on the DOM, which prevents ALL user interaction: no clicking, no text selection, no gutter line number clicks. Users reported 'Editor' toggle does nothing because the rendered CodeMirror was completely unresponsive. Fix: remove EditorView.editable.of(false) from the merge view extensions. EditorState.readOnly.of(true) alone prevents editing while allowing clicking, text selection, and gutter interaction to work normally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The merge view now has asymmetric editor configs: - Original (a/left): readonly + non-editable (view-only reference) - Modified (b/right): fully editable with history, undo/redo, bracket completion, indent-with-tab, and active line highlighting Also adds getModifiedContent(instanceId) JS API so the Blazor side can retrieve user edits from the modified pane. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a '💾 Apply' button in the diff editor header that: - Reads the modified content from the CodeMirror editor via JS interop - Writes it back to the file on disk via the component chain - Shows brief ✅ Saved / ❌ Error feedback with auto-clear Component chain: DiffView → ChatMessageItem → ChatMessageList → ExpandedSessionView → File.WriteAllTextAsync with path traversal guard. New model: DiffApplyEditRequest(FileName, Content) in DiffParser.cs. New JS API: PolyPilotCodeMirror.getModifiedContent(instanceId). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ws line endings - Wire OnCommentRequested and OnApplyEdit callbacks to DiffView in ChatMessageType.Diff case, enabling line-number click → comment box → send to chat workflow for standalone diff messages - Use explicit '\n' in ReconstructOriginal/ReconstructModified instead of AppendLine() which emits \r\n on Windows, fixing 6 test failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Move comment box from fixed position to inline (right next to clicked line in table view, below editor in editor view) - Add side label (original/modified) to comment box header - Add scroll-into-view + auto-focus for comment textarea - Add .diff-comment-row and .diff-comment-side CSS styles - Allowlist DiffView.razor.css 0.85em in FontSizingEnforcementTests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ance Adds a simulateLineClick() function that directly invokes the .NET HandleEditorLineClick interop, bypassing DOM event limitations in WebView for testing. Also stores commentMeta on the instance map entry so test helpers can access dotNetRef and file metadata. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔴 NEW-1: Fix Apply writing blank placeholder lines to disk (data loss) - ReconstructOriginal/Modified now accept optional fileLines parameter - When provided, inter-hunk gaps are filled with real file content - DiffView gets FileContentProvider parameter to load files at init - ExpandedSessionView wires LoadFileContentAsync from working directory - Trailing lines after last hunk are also appended 🔴 NEW-2: Fix path traversal bypass in Apply handler - Add trailing directory separator before StartsWith check - Use OrdinalIgnoreCase for Windows/macOS case-insensitive filesystems - Same fix applied to LoadFileContentAsync 🟡 NEW-3: Renamed file comment uses correct filename - Original-side line comments now use OldFileName for renamed files 🟡 NEW-4: _applySaveStatus cleared on file switch - SelectFile() now resets _applySaveStatus to prevent bleed 🟢 NEW-5: Cache AddedLineCount/RemovedLineCount - DiffFile now caches these O(n) computed properties via nullable backing fields 🟢 NEW-6: Fix stale CSS comment on .cm-merge-revert - Updated comment to reflect that modified side is editable Tests: 8 new tests (3411 total, 0 failures) - ReconstructModified/Original with fileLines gap-fill - Trailing lines append - Backward compat (blank gaps without fileLines) - Cached line count verification - Path traversal: sibling dir, valid subpath, dot-dot escape Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…named file comments 🟡 NEW-1: CanApplyEdit now requires FileContentProvider to be set. Inline chat diffs (ChatMessageItem) don't provide a FileContentProvider, so the Apply button is hidden — preventing blank-gap data loss on that path. 🟡 NEW-2: Editor-view line comments on renamed files now use the correct filename per side. createMergeView accepts oldFileName parameter; the original-side click handler uses oldFileName while modified side uses the new filename. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CodeMirror 6 Diff Editor
Adds a side-by-side diff editor using CodeMirror 6, integrated into the existing DiffView component.
Features
Technical Details
codemirror-src/via esbuild (reproducible)DiffParser.ReconstructOriginal/ReconstructModifiedextract both sides from parsed diffsChangeModelAsynctest compilation errors (missing named param)Verified