Restore embedded inline comments in Plan/File/Diff views (#456)

* fix(plan-comments): restore embedded inline comment widgets in Plan view

Reinstate CodeMirror block-widget comments so plan annotations stay anchored to selected lines and preserve drag/selection behavior without floating overlays.

* fix(file-comments): return Files editor comments to embedded widgets

Use inline block widgets for file drafts while keeping full-path draft scoping, so similarly named files no longer risk comment collisions.

* fix(diff-comments): render inline comments through annotation portals

Replace absolute floating positioning with annotation-target portals to keep diff comments attached to their lines across shadow DOM updates.

* chore(comments): remove deprecated floating comment hook

Drop the unused floating-comment implementation now that plan, file, and diff views all use embedded comment rendering paths.

* fix(codemirror): expose gutter width as CSS variable

Measure the current CodeMirror gutter and publish --oc-editor-gutter-width on the editor host so inline widgets can size to the visible code area without hardcoded dimensions.

* fix(context-panel): publish panel width for embedded widgets

Set --oc-context-panel-width on the context panel in both docked and expanded modes so comment widgets can follow the active panel width dynamically.

* fix(file-comments): constrain inline input to editor content width

Use context-panel and editor-gutter CSS variables to cap comment input width to the visible editor content area, keeping action buttons fully visible in no-wrap mode.

* fix(file-comments): constrain inline comment cards to content area

Apply the same variable-based width cap to saved comment cards so card actions stay visible when long lines force horizontal scrolling.

* fix(diff-comments): stabilize new comment annotation identity

Derive new-comment annotation ids from selection side and line range, and reuse that id for portal target lookup and keys to avoid remount glitches.
This commit is contained in:
Nelson Pires
2026-02-20 16:52:01 +02:00
committed by GitHub
parent 1d6895a9e7
commit 49170fe242
8 changed files with 309 additions and 524 deletions
+62 -19
View File
@@ -66,7 +66,8 @@ import { useUIStore } from '@/stores/useUIStore';
import { useFilesViewTabsStore } from '@/stores/useFilesViewTabsStore';
import { useGitStatus } from '@/stores/useGitStore';
import { useInlineCommentDraftStore } from '@/stores/useInlineCommentDraftStore';
import { useFloatingComments } from '@/components/comments/useFloatingComments';
import { InlineCommentCard } from '@/components/comments/InlineCommentCard';
import { InlineCommentInput } from '@/components/comments/InlineCommentInput';
import { opencodeClient } from '@/lib/opencode/client';
import { useDirectoryShowHidden } from '@/lib/directoryShowHidden';
import { useFilesViewShowGitignored } from '@/lib/filesViewShowGitignored';
@@ -1798,23 +1799,65 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
return sessionDrafts.filter((d) => d.source === 'file' && d.fileLabel === selectedFile.path);
}, [selectedFile, currentSessionId, allDrafts]);
const floatingComments = useFloatingComments({
editorView: editorViewRef.current,
wrapperRef: editorWrapperRef,
fileDrafts: filesFileDrafts,
editingDraftId,
commentText: '',
lineSelection,
isDragging,
fileLabel: selectedFile?.path ?? '',
onSaveComment: handleSaveComment,
onCancelComment: () => setLineSelection(null),
onEditDraft: (draft) => {
setEditingDraftId(draft.id);
setLineSelection(null);
},
onDeleteDraft: (draft) => removeDraft(draft.sessionKey, draft.id),
});
const blockWidgets = React.useMemo(() => {
const widgets: import('@/components/ui/CodeMirrorEditor').BlockWidgetDef[] = [];
for (const draft of filesFileDrafts) {
if (draft.id === editingDraftId) {
widgets.push({
afterLine: draft.endLine,
id: `edit-${draft.id}`,
content: (
<InlineCommentInput
key={draft.id}
initialText={draft.text}
fileLabel={selectedFile?.path}
lineRange={{ start: draft.startLine, end: draft.endLine }}
isEditing={true}
onSave={handleSaveComment}
onCancel={() => setEditingDraftId(null)}
/>
),
});
} else {
widgets.push({
afterLine: draft.endLine,
id: `card-${draft.id}`,
content: (
<InlineCommentCard
key={draft.id}
draft={draft}
onEdit={() => {
setEditingDraftId(draft.id);
setLineSelection(null);
}}
onDelete={() => removeDraft(draft.sessionKey, draft.id)}
/>
),
});
}
}
if (lineSelection && !editingDraftId && !isDragging) {
widgets.push({
afterLine: lineSelection.end,
id: 'files-new-comment-input',
content: (
<InlineCommentInput
key="new-comment"
initialText=""
fileLabel={selectedFile?.path}
lineRange={lineSelection}
isEditing={false}
onSave={handleSaveComment}
onCancel={() => setLineSelection(null)}
/>
),
});
}
return widgets;
}, [filesFileDrafts, editingDraftId, lineSelection, isDragging, selectedFile?.path, handleSaveComment, removeDraft]);
const fileViewer = (
<div
@@ -2184,6 +2227,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
onChange={setDraftContent}
extensions={editorExtensions}
className="h-full"
blockWidgets={blockWidgets}
onViewReady={(view) => {
editorViewRef.current = view;
window.requestAnimationFrame(() => {
@@ -2269,7 +2313,6 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
},
}}
/>
{floatingComments}
</div>
)}
</ScrollableOverlay>